[StreamWrite]
잘 돌아가던 프로그램이 갑자기 뻗는다는 연락을 받았다.
예외처리도 어느정도 되어있다고 생각했는데 프로그램이 뻗는 크리티컬한 에러가 발생되었다는게 너무 당황스러웠다.
원인은 얼마전 부사수가 패치한 로그작성 Stream에 있었다. StreamWrite는 System.IO를 참조하면 사용할 수 있는 매서드이다.
뜯어보면 TextWriter를 상속받고 Task Async도 지원한다. 구글에 '텍스트 파일 출력' 이나 '로그작성' 이라고 검색해보면 대부분 Stream 사용을 추천한다. 물론 검증되어있고 사용하기 편리하지만 예외처리는 해주어야한다.
별도 선언 없이도 사용이 가능하고 경로와 데이터만 입력되면 술술 적어주는 장점이 있긴 하지만 예외발생 시 경로가 네트워크 드라이브거나 리소스를 많이 먹는경우 크리티컬하게 작용할 수 있으므로 주의있게 사용해야한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using System.Threading.Tasks;
namespace System.IO
{
[ComVisible(true)]
public class StreamWriter : TextWriter
{
public static readonly StreamWriter Null;
public StreamWriter(Stream stream);
public StreamWriter(string path);
public StreamWriter(Stream stream, Encoding encoding);
public StreamWriter(string path, bool append);
public StreamWriter(Stream stream, Encoding encoding, int bufferSize);
public StreamWriter(string path, bool append, Encoding encoding);
public StreamWriter(Stream stream, Encoding encoding, int bufferSize, bool leaveOpen);
[SecuritySafeCritical]
public StreamWriter(string path, bool append, Encoding encoding, int bufferSize);
public virtual bool AutoFlush { get; set; }
public virtual Stream BaseStream { get; }
public override Encoding Encoding { get; }
public override void Close();
public override void Flush();
[ComVisible(false)]
public override Task FlushAsync();
public override void Write(char value);
public override void Write(char[] buffer, int index, int count);
public override void Write(string value);
public override void Write(char[] buffer);
[ComVisible(false)]
public override Task WriteAsync(char value);
[ComVisible(false)]
public override Task WriteAsync(string value);
[ComVisible(false)]
public override Task WriteAsync(char[] buffer, int index, int count);
[ComVisible(false)]
public override Task WriteLineAsync();
[ComVisible(false)]
public override Task WriteLineAsync(char value);
[ComVisible(false)]
public override Task WriteLineAsync(string value);
[ComVisible(false)]
public override Task WriteLineAsync(char[] buffer, int index, int count);
protected override void Dispose(bool disposing);
}
}
|
cs |
[예외 원인]
이번 예외는 'System.ArgumentException' 으로 경로상에 유효하지 않은 문자가 포함되어 Window에서 처리가 불가능 할 때 발생된다.
Notepad를 열고 아무 데이터나 작성 한 뒤 저장할때 *만 추가해도 저장이 눌리지 않는것을 확인 할 수 있다.
물론 Try Catch를 사용하면 예외는 처리할 수 있겠지만 그럼 정작 중요한 로그가 남지 않기때문에 반드시 예외처리가 필요하다.
구글에 파일 이름으로 사용할 수 없는 문자를 검색하면 상세 내용을 확인할 수 있다.
(경로)
기본적으로 운영체제에서 특수문자도 단독으로 사용되거나 특정 위치에서 사용 할 경우 특수한 역할을 수행하는 경우가 존재한다.
운영체제별로 파일명으로 사용할 수 없는 문자가 지정되어 있으므로 사용자는 주의해서 사용해야 하고, 그걸 방지하기 위해서 개발자는 적용 환경에 따라 예외처리를 진행해야 한다.
1. / \ 디렉터리의 구분자로 쓰인다
2. : 드라이브의 기호로 사용된다.
3. * ? 와일드 카드로 쓰인다
4. " 경로의 시작과 끝을 나타낸다.
5. < > | 리다이렉트, 파이프 등 특수 문법에 사용된다.
필자의 경우 해당 설비의 LOT에 사용자가 *을 입력함으로 써 예외가 발생되어 프로그램이 다운되는 현상이 발생된 것이다.
[예외함수 작성]
일반적으로 사용되고 있는 SeriLog나 Log4Net 등을 사용하면 어느정도 커버가 가능하지만 Stream의 경우 통신에서도 사용되기 때문에 특수문자는 예외처리가 반드시 필요하다.
그리고 이미 다 알고있겠지만 다중스레드 환경을 구성할때는 반드시 크리티컬섹션(lock)을 사용해야 한다는 점도 잊지 말아야 한다.
아래 있는 ChangeSepcialToUnderbar 함수를 참고하면 된다. 특수문자를 '_' 언더바로 바꾸는 함수로 언더바는 파일명에 큰 지장이 없기때문에 해당 문자열로 치환한다. 공용으로 사용하는 유틸함수는 가능하면 모듈화를 진행하지만 편의를 위해서 전역으로 선언했다.
특수하게 .(점 또는 닷)은 단일로 사용될 수 없기 때문에 문자열이 하나 일 경우 언더바로 치환되도록 하였다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
using System;
using System.Diagnostics;
using System.IO;
namespace StreamWriterTest
{
class Program
{
static void Main(string[] args)
{
string sSavePath;
string sFolderPath;
string sFileName;
string sTemp = "Data";
object logLock = "";
sFolderPath = @"C:\Users\Yang\Documents\test\";
sFileName = "test*/<:>?|..txt";
sSavePath = sFolderPath + sFileName; // Exception : System.ArgumentException
sSavePath = sFolderPath + ChangeSpecialToUnderbar(sFileName);
lock (logLock)
{
using (StreamWriter writer = new StreamWriter(sSavePath, true))
{
try
{
writer.WriteLine(DateTime.Now + " - " + sTemp);
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
Debug.WriteLine(sSavePath + "경로에 저장 완료");
}
}
}
static string ChangeSpecialToUnderbar(string checkString)
{
string sTemp;
// 문자열 앞 뒤 공백 제거
sTemp = checkString.Trim();
// Window 파일명 설정 불가 특수문자 제거
///////////////////////////////////////
// *, /, :, ?, ", <, >, |
///////////////////////////////////////
sTemp = sTemp.Replace("*", "_");
sTemp = sTemp.Replace("/", "_");
sTemp = sTemp.Replace(":", "_");
sTemp = sTemp.Replace("?", "_");
sTemp = sTemp.Replace("\"", "_");
sTemp = sTemp.Replace("<", "_");
sTemp = sTemp.Replace(">", "_");
sTemp = sTemp.Replace("|", "_");
// .(점)은 단독으로 쓰일 수 없으므로 치환한다.
if (sTemp.Length == 1)
sTemp = sTemp.Replace(".", "_");
return sTemp;
}
}
}
|
cs |
[요약]
StreamWrite로 파일 생성 시 파일 경로에 특수문자가 포함되면 예외가 발생한다.
반드시 예외처리를 해줘야 한다.