office 문서의 관리 / 편집을 프로그램으로 하기 위해서는 vb나 c#을 이용해야하죠. 특히 다음과 같은 마소의 본연 라이브러리로 하는데요, 기술의 발달로 .Net Framework 가 아닌 .NET core를 넘어 .NET Stardard에서
Microsoft.Office.Interop.Word
Microsoft.Office.Interop.Excel
Microsoft.Office.Interop.Powerpoint
Microsoft.Office.Interop.Outlook
의 com api를 사용하면 다음과 같은 에러메시지를 맞게 되었죠.
일단 이유에 대해서 설명드리겠습니다. 윈도우 전용이었던 .Net Framework 에서 cross platform을 추구하는 .NET core, .NET Standard로 넘어오면서 기본 세팅하에서는 com api를 인식할 수 없게 되었어요. com api는 윈도우에만 존재하기 때문이죠.
구글링했을때는 ".net core에서는 사용할 수 없다." 라고 기술되어 포기했었는데, 직접참조하면 된다고해서 도전을 다시 해봤죠. 역시나 안됐어요.
그렇다면 어떻게 세팅하면 사용할 수 있을까?
방법은 의외로 간단했어요.
platform 세팅을 any 에서 window로 바꾸는 것이죠.
1. 솔루션 바로 하단의 어플리케이션을 우클릭
2. 애플리케이션 -> 일반
3. 대상 OS를 찾아 기본값인 (없음)을 클릭
4.Windows를 클릭
5. 솔루션 내 종속성 우클릭 후 COM 참조 추가
(Nuget으로 microsoft.office.interop 라이브러리를 추가하면 에러가 나던데, 성공하신분 연락 또는 댓글로 방법 남겨주시기 바랍니다!!)
6. 필요한 라이브러리 추가 ( 스크린샷 예제에서는 Microsoft.Office.Interop.Word 를 사용하기 위해 word com object를 추가)
word com 객체를 추가하였고 이제 using Microsoft.Office.Interop.Word를 사용하면 라이브러리 사용이 가능하답니다.
주의할점!!!
1. 플랫폼을 바꾸면 어떻게 되느냐 - 당연히 지정한 플랫폼에서만 사용가능합니다. window10/11 등의 window os가 설치된 PC 또는 window server에서만 사용이 가능한 것이죠.
2. 위 예제에서 Microsoft Word 16.0 Object Library가 안보이시는 분들 특히, 이 com 객체를 추가하여 라이브러리를 사용하기 위해서는 개발된 어플리케이션이 설치될 pc에 office가 설치되어 있어야 합니다.
3. 이 포스팅을 업로드하는 시점까지 확정된 microsoft의 com api 지원기간은 2029년까지로 알려져있습니다.
Get 방식의 http method는 정보의 조회 등인 경우가 많아 대부분 200번 즉 response.StatusCode 가 HttpStatusCode.OK일 거예요. 다만, 구체적으로 반환되는 HTTP Status Code는 API를 제작한 개발사/개발자에 문의 또는 설명문서를 참조하여 정하시는 것이 좋겠습니다.
다만, 200번대가 보통 성공을 나타낸다고 약속되어져 있습니다. (제가 약속한거 아닙니다.)
따라서 다음과 같은 방법으로 200번대면 성공이라고 가정, 아니면 실패를 예외를 던지는 속성과 함수도 존재합니다.
// response.IsSuccessStatusCode: response가 200번 대면 true를 반환하는 bool값
if(response.IsSuccessStatusCode) {
// 성공
}
// if문도 필요 없이 200번대면 다음 레코드를 읽고, 200번대가 아니면 예외를 던지는 함수
response.EnsureSuccessStatusCode();
응답이 성공했으면 원하는 응답을 읽어와야하고, 그 방법에는 여러가지가 있을텐데요. 두가지를 설명드리겠습니다.
1. 응답을 모두 string으로 받아오는 방법
c#의 함수나 속성명은 상당히 직관적입니다.
[응답]의 [내용]을 [문자열]으로 불러오겠습니다.
(response)(content)(string)
var responseContent = await response.Content.ReadAsStringAsync();
그런데 대부분은 string 자체를 사용하진 않을 것입니다. 약속된 API라면 반환값이 json형태의 문자열이기 때문이죠.
따라서 json형태의 문자열을 다음과 같이 클래스로 바꿔줍니다.( System.Text.Json 필요)
var result = JsonSerializer.Deserialize<ResultClass>(responseContent, new JsonSerializerOptions());
JsonSerializerOptions 옵션 등과 같은 System.Text.Json의 고유 기능은 제가 자세히 설명해놓은 글
저의 경우 NewtonSoft.Json은 .Net Framework에서는 많이 사용했지만. Net Standard에 와서는 내장된 System.Text.Json을 적극 사용하고 있습니다. 특히 아래 2번 방법과 JsonSerializerOptions 을 공유하고 있기 때문에 사용하시는 것을 권장드립니다.
2. 응답내용을 불러올때 클래스로 만드는 방법입니다. (using System.Net.Http.Json 필요)
ResultClass result = await response.Content.ReadFromJsonAsync<ResultClass>();
형태로 사용하거나 var로 선언하긴하지만, 여러분에게 명시적으로 보여드리기 위해 클래스로 선언부를 작성하였습니다.
개발 초기에는 응답내용을 전부 로그를 찍기 위해 1번으로 시작할때도 있지만, 주로 2번을 사용하고 있습니다.
ReadFromJsonAsync의 정의를 보면, System.Text.Json의 JsonSerializerOptions 옵션을 공유합니다.
null처리, 기본값 처리 등을 손쉽게 할 수 있고, 그에 따라 api의 상세한 요구조건을 컨트롤 할 수 있으니 반드시 알아두시면 좋겠습니다.
ResultClass가 무엇인가요? 라고 물으실 수 있을 것 같아요. 아무렇게나 작성한 임의의 클래스입니다.
{"number": 1} 이라는 형태의 json이 왔고 약속된 형태라면,
ResultClass은 아래와 같이 정의가 되었을 것입니다.
class ResultClass{
public int Number {get; set;}
}
// 또는
class ResultClass{
[JsonPropertyName("number")]
public int Sequence {get; set;}
}
serialize했을때 json이 되는 클래스를 만들었다라고 보시면 되겠습니다.
이 포스트는 json, json과 클래스의 관계에 대한 설명글이 아니기 때문에 이 정도만 작성하도록하고, 여태까지 설명 내용을 한 번에 보실 수 있도록 담아보겠습니다.
// json문자열을 역직렬화할 Model 클래스
class ResultClass{
[JsonPropertyName("number")]
public int Sequence {get; set;}
}
// HttpClient 선언
HttpClient httpClient = new(){
BaseAddress = new Uri("https://api.example.com/"),
Timeout = TimeSpan.FromSeconds(30) // 굳이 적지 않아도 되나 기본값이 30초
};
// 필요시 헤더 추가
if (!httpClient.DefaultRequestHeaders.Contains("Key"))
httpClient.DefaultRequestHeaders.Add("Key", "Value");
// Get 방식 호출
HttpResponseMessage response = await httpClient.GetAsync("/class/students");
// 성공 여부 검증
response.EnsureSuccessStatusCode();
// 성공시 json을 역직렬화하여 변수에 담는다.
var result = await response.Content.ReadFromJsonAsync<ResultClass>();
SMS(문자) 발송, 이메일 발송, 우리나라 기준 한정으로 카카오 메시지 발송이 있을 것입니다.
이번 포스트에서는 그중에서도 이메일 발송에 대해 알아보도록 하겠습니다.
대개 email을 발송하기 위해서는
이메일을 발송하는 이메일 서버(SMTP)와 이메일을 발송할 이메일 계정이 필요합니다.
C#에서는
.Net 라이브러리 중 System.Net.Mail 라이브러리 내
SmtpClient 클래스로 SMTP와 연결하고,
MailMessage 클래스로 이메일을 작성할 수 있습니다.
다만 이 포스트에서는 메일서버 구축이 아닌 보내기에 초점을 두었기 때문에 SMTP서버의 구축 관련한 내용은 다루지 않겠습니다.
참고로 무료로 풀려있는 SMTP 서버들이라 하더라도 일정 갯수가 넘어가게 발송하게되면 상업적용도로 간주되어 결제를 요구하기도 하니, 상업적용도로 사용하실 분들은 SMTP서버를 제공하는 각 기업에 절차에 맞게 결제 등을 하여 사용하시면 됩니다. c# 개발자 또는 엔지니어라면 아마도 Microsoft, 한국에서는 역시 naver, 그리고 kakao도 daum을 인수했기 때문에 smtp서버가 있을 것이라 판단됩니다.
SqlClient도 그렇고 호출을 담당하는 클래스들은 ~Client로 명명하는 것이 규칙으로 보입니다.
먼저, SmtpClient를 선언합니다.
using SmtpClient SmtpServer = new(smtpAddress)
{
Port = smtpPort,
EnableSsl = true,
UseDefaultCredentials = false,
DeliveryMethod = SmtpDeliveryMethod.Network,
Credentials = new NetworkCredential(mailSender, mailSenderPassword)
};
smtpAddress 자리에는 SMTP Server의 주소를 입력하면되고
smtpPort에는 SMTP Server의 열려있는 포트를 이용하면 됩니다. 보통 암호화 보안연결을 사용하면 465나 587, 사용하지 않으면 25가 기본값으로 알려져있습니다.(https가 443, http가 80 인 것과 동일하게 생각하시면 되겠습니다.)
SMTP서버를 직접 구축하신 경우에는 네트워크에 지정한 포트번호를 이용하시면 되겠습니다.
SMTP에 연결할 계정은 NetworkCredential() 클래스에 (계정, 비밀번호) 형태의 생성자가 있으므로, SmtpClient 클래스의 인스턴스 필드 중 Credentials로 입력해주면 됩니다.
SmtpClient는 IDisposabled이 구현되어있어서 선언부 앞에 using을 붙여주면 사용하지 않을때 메모리를 해체할 수 있습니다.
Visual Studio 기준으로 ctrl버튼을 누른채로 작성한 SmtpClient를 클릭하면, 해당 클래스를 모두 보실 수 있는데요.
이외에도 여러 필드가 있지만 포스트의 예제에서는 저 정도만 사용하도록 하겠습니다.
SMTP를 연결했으면 이제는 이메일을 작성해야겠죠.
using MailMessage email = new()
{
From = new MailAddress(mailSender),
Subject = emailSubject,
BodyEncoding = Encoding.UTF8,
IsBodyHtml = true,
Body = "본문 내용",
};
만일 받는이가 1명이라면 MailMessge(string from, string to)라는 생성자가 존재하기 때문에 아래와 같이 선언하는 방법도 있습니다.
왜 선언부에 To, Cc, Bcc를 안적으셨냐고 물으실 수 있는데요, 여러명한테 보낼 수 있다보니 세 변수는 컬렉션으로 구성되어있어요. (class MailAddressCollection : Collection ) 한명한테만 보낸다면 모르겠지만, 여러명한테도 보낼 상황이 있는 작업물이라면 선언하고 추가하는 방식이 좋습니다.
Sicos1977/MSGReader: C# Outlook MSG file reader without the need for Outlook (github.com)
Msgreader는 이메일 파일인 .msg와 .eml확장자 파일을 읽을 수 있도록하는 라이브러리입니다.
원본 github주소는 위 그림을 클릭하면 이동하실 수 있습니다.
이메일 파일에 담긴 이메일 정보(보낸이, 받는이, 보낸시각, 받은시각, cc, bcc, 메일제목, 메일내용, 첨부파일 등)의 정보를 읽을 수 있도록합니다.
상세한 예외처리는 제외하고 용법을 보겠습니다.
먼저, 메일정보를 쉽게 주고받을 수 있도록 프로퍼티를 충분히 가진 클래스를 선언하겠습니다.
class MailFile {
public string FileDirectory { get; set; } // 파일 경로
public string FileName { get; set; } // 파일명
public string FilePath { get; set; } // 파일경로 + 파일명
public string Originator { get; set; } // 보낸사람
public string Addressee { get; set; } // 받는사람
public string CC { get; set; } // CC
public string BCC { get; set; } // BCC
public DateTime SentDate { get; set; } // 발신 시간
public DateTime ReceivedDate { get; set; } // 수신 시간
public MailFile(string fileDirectory, string fileName)
{
FileDirectory = fileDirectory;
FileName = fileName;
FilePath = Path.Combine(fileDirectory, fileName);
// 그냥 string으로 더하지 않고 Path.Combine으로 쓴이유를 모른다면 독자는 꼭 찾아봐야한다!
}
}
예제이기 때문에 msg인지 eml파일인지 여부를 비교할 대상은 그냥 선언하겠습니다.
(여기서는 확장자로만 구분합니다.)
// 메일 파일을 읽기전에 있어야할 것들을 기입한 부분입니다.
// msg인지 eml인지 비교할 확장자,
// MailFile 클래스의 선언
// 생성된 첨부파일을 다운받을 경로 등
string msgExtention = ".MSG";
string emlExtention = ".EML";
string mailFileDirectory = @"파일경로, \가 보통들어가서 @를 붙임";
string mailFileName = @"파일명";
var mailFile = new MailFile(mailFileDirectory, mailFileName);
string downloadDirectory = @"첨부파일이있다면 다운받을 경로이다.";
다음은 메일파일의 정보가 있는지 여부를 받아서 받아오는 것이다.
FileInfo mailFileInfo = new FileInfo(mailFile.FilePath);
if (fileInfo.Exists && fileInfo.Length > 0)
// fileInfo.Exists는 파일이 존재하는지 여부를 리턴
// fileInfo.Length는 파일의 크기를 byte단위로 리턴
{
if (mailFileInfo.Extension.ToUpper().Equals(msgExtention))
// 파일 경로 비교 ToUpper()를 해도되고 StringComparison.OrdinalIgnoreCase 를 사용해도된다.
{
using var msgFile = new MsgReader.Outlook.Storage.Message(mailFilePath);
// 메일 파일의 경로에 따라 여기서는 확장자값이 .MSG이면 msg인스턴스(Storage.Message)를 생성한다.
// 아래는 읽으면 감이 올 것이다. 각 정보를 받아오는 것이고
// 여기에 매칭되도록 초반에 MailFile을 선언했다.
// 실제 사용시에는 MailFile에 첨부파일 존재여부도 넣어주면 좋다.
mailFile.Originator = msg.Sender?.DisplayName; // 보낸이
mailFile.Addressee = msg.GetEmailRecipients(RecipientType.To, false, false); // 받는이
mailFile.CC = msg.GetEmailRecipients(RecipientType.Cc, false, false); // CC
mailFile.BCC = msg.GetEmailRecipients(RecipientType.Bcc, false, false); // BCC
mailFile.SentDate = msg.SentOn ?? DateTime.MinValue; // 보낸시각
mailFile.ReceivedDate = msg.ReceivedOn ?? DateTime.MinValue; // 받는시각
// 첨부파일은 아래와 같은방법으로 가져올 수 있다.
// #### 첨부파일류들은 data[]로 파일데이터를 들고있기때문에 아주 무겁다. ####
// ## 그러나 IDisposable을 구현하고 있어서 dispose()나 using을 사용할 수 있다.
// ## 해당부분은 여기에서는 포함하고 있지 않아서 참고하여 개발시 이 부분을 반드시 확인하기바란다.
// ## 아니면 메모리가 탈탈 털리는 현상을 볼 지도...
// ## 또한 파일데이터를 들고있기때문에 너무 용량이 큰 친구들은 이것으로 첨부파일 뽑으려면
// ## 메모리가 아주커야할 것이다... 파일 용량에 꼭 제한을 두자.
// ## 도입부에 FileInfo 선언했는데 거기서 처리할 수 있다.
var attachmentsObject = msgFile.Attachments;
attachmentsObject.ForEach(attachment =>
{
try
{
if (!Directory.Exists(downloadDirectory)){ // 첨부파일을 다운받을 경로가 없으면 만든다.
Directory.CreateDirectory(downloadDirectory);
}
if (attachment != null && attachment is Storage.Attachment) // 일반적인 첨부파일이면
{
var attachmentCast = attachment as Storage.Attachment ?? throw new Exception($"형변환했는데 null이면 던질 예외");
string attachmentPath = Path.Combine(downloadDirectory, attachmentCast.FileName); // 내려받을 첨부파일 path
File.WriteAllBytes(attachmentPath, attachmentCast.Data); // 다운로드
}
else if (attachment != null && attachment is Storage.Message) // 앞에 using var msgFile = new MsgReader.Outlook.Storage.Message(mailFilePath); 부분을 보면 눈치 챘을 수도 있지만 이는 첨부파일이 MSG파일인 경우이다.
{
var attachmentCast = attachment as Storage.Message ?? throw new Exception($"형변환했는데 null이면 던질 예외");
string attachmentPath = Path.Combine(downloadDirectory, attachmentCast.FileName);
attachmentCast.Save(attachmentPath); // Storage.Message는 Save함수를 사용하여 파일을 내려받아야 한다.
}
else
{
// 둘다 아닌경우에 취할 조치를 하면 된다. 예외처리를 해도 좋다.
}
}
catch (Exception ex)
{
Log.Error(ex.ToString());
}
});
}
else if (mailFileInfo.Extension.ToUpper().Equals(emlExtention))
{
var emlFile = MsgReader.Mime.Message.Load(mailFileInfo); // eml 파일을 읽는 방법이다.
if (emlFile.Headers != null) // eml 파일의 메일 파일정보 불러오기는 복잡하다... msgReader 공식 문서를 참조하면 이렇게 해야한다.
{
if (emlFile.Headers.From != null && !string.IsNullOrEmpty(eml.Headers.From.DisplayName))
{ mailFile.Originator = emlFile.Headers.From.DisplayName.ToString().Replace("\"", string.Empty); }
else if (eml.Headers.From != null && !string.IsNullOrEmpty(eml.Headers.From.Address))
{ mailFile.Originator = emlFile.Headers.From.Address.ToString().Replace("\"", string.Empty); }
else { mailFile.Originator = ""; }
emlFile.Headers.To.ForEach(to => mailInfo.Addressee += "," + to.ToString());
if (mailFile.Addressee == null) { mailFile.Addressee = ""; }
else { mailFile.Addressee = mailFile.Addressee.Substring(1).Replace("\"", string.Empty); }
emlFile.Headers.Cc.ForEach(cc => mailFile.CC += "," + cc.ToString());
if (mailFile.CC == null) { mailFile.CC = ""; }
else { mailFile.CC = mailFile.CC.Substring(1).Replace("\"", string.Empty); }
emlFile.Headers.Bcc.ForEach(bcc => mailFile.BCC += "," + bcc.ToString());
if (mailFile.BCC == null) { mailFile.BCC = ""; }
else { mailFile.BCC = mailInfo.BCC.Substring(1).Replace("\"", string.Empty); }
if (emlFile.Headers.DateSent != null) { mailFile.SentDate = emlFile.Headers.DateSent; }
else { mailFile.SentDate = DateTime.MinValue; }
if (emlFile.Headers.Received != null && emlFile.Headers.Received.Count > 0)
{
mailFile.ReceivedDate = emlFile.Headers.Received.Last().Date;
}
else
{
mailFile.ReceivedDate = DateTime.MinValue;
}
} // 여기까지가 메일정보 읽기
ObservableCollection<MessagePart> attachments = emlFile.Attachments;
var attachmentsCount = attachments.Count;
foreach (var attachment in attachments) // eml파일은 정보읽기는 복잡하지만 첨부파일 다운로드는 간단하다.
{
try
{
if (!Directory.Exists(downloadDirectory))
Directory.CreateDirectory(downloadDirectory);
if (attachment.IsAttachment)
attachment.Save(new FileInfo(Path.Combine(downloadDirectory, attachment.FileName)));
}
catch (Exception ex)
{
Log.Error(ex.ToString());
}
}
}
}
생각보다 코드가 길기 때문에 설명도 코드블럭내에 같이 적어놨습니다. 특히 ####, ## 붙은 부분을 반드시 읽어서 참고시에 메모리 등 관리문제를 최대한 방지 하시기 바랍니다.
class Example
{
public List<Fruit> Fruits;
public string Str;
public int Number = 0;
public int NumberDefault0 = 0;
public bool IsOk;
public string? NullableStr;
public int? NullableInteger;
}
class Fruit
{
public string Name;
public int price;
}
그리고 아래는 실행할 소스 입니다.
using Newtonsoft.Json;
Example example = new()
{
Str = "문자열",
Number = 10,
NumberDefault0 = 0,
IsOk = true,
NullableStr = null,
NullableInteger = null
};
var exampleJson0 = JsonConvert.SerializeObject(example);
var exampleJson1 = JsonConvert.SerializeObject(example, Formatting.None);
var exampleJson2 = JsonConvert.SerializeObject(example, Formatting.Indented);
var exampleJson3 = JsonConvert.SerializeObject(example, Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
var exampleJson4 = JsonConvert.SerializeObject(example, Formatting.None, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Ignore });
var exampleJson5 = JsonConvert.SerializeObject(example, Formatting.Indented, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Ignore });
var exampleJson6 = JsonConvert.SerializeObject(example, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
Console.WriteLine($"exampleJson0: {exampleJson0}");
Console.WriteLine($"exampleJson1: {exampleJson1}");
Console.WriteLine($"exampleJson2: {exampleJson2}");
Console.WriteLine($"exampleJson3: {exampleJson3}");
Console.WriteLine($"exampleJson4: {exampleJson4}");
Console.WriteLine($"exampleJson5: {exampleJson5}");
Console.WriteLine($"exampleJson5: {exampleJson6}");
인스턴스를 json형태로 변환했는데 json형태의 문자열을 클래스의 인스턴스로 변환하는 것도 있어야겠죠?
우선 이해를 돕기 위한 코드를 작성하겠습니다.
class Example
{
public List<Fruit> Fruits;
public string Str;
public int Number = 0;
public int NumberDefault0 = 0;
public bool IsOk;
public string? NullableStr;
public int? NullableInteger;
public override string? ToString() => JsonConvert.SerializeObject(this);
public string? ToStringJson() => JsonConvert.SerializeObject(this, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore});
public string? ToString2() =>
"Fruits=" + (Fruits == null ? "null" : Fruits.ToString()) +
"\n&Number=" + Number +
"\n&NumberDefault0=" + NumberDefault0 +
"\n&IsOk=" + IsOk +
"\n&NullableStr=" + (NullableStr == null ? "null" : NullableStr.ToString()) +
"\n&NullableInteger=" + (NullableInteger == null ? "null" : NullableInteger.ToString());
}
class Fruit
{
public string Name;
public int price;
}
Example 클래스를 출력 가능하게 바꾸는 클래스를 기본 ToString()을 override 한것 외에도 두개를 더 만들었고
다음은 실행 코드 입니다.
Example example = new()
{
Str = "문자열",
Number = 10,
NumberDefault0 = 0,
IsOk = true,
NullableStr = null,
NullableInteger = null
};
// 1.------------------------------------------------
var exampleJson0 = JsonConvert.SerializeObject(example);
Console.WriteLine($"example: {example}");
Console.WriteLine($"example.ToStringJson(): {example.ToStringJson()}");
Console.WriteLine($"example.ToString2(): {example.ToString2()}");
// 2.------------------------------------------------
var ex0 = JsonConvert.DeserializeObject(exampleJson0);
Console.WriteLine($"{ex0.GetType().Name}: {ex0}");
//Console.WriteLine($"ex0.ToStringJson(): {ex0.ToStringJson()}");
//Console.WriteLine($"ex0.ToString2(): {ex0.ToString2()}");
// 3.------------------------------------------------
var ex1 = JsonConvert.DeserializeObject<Example>(exampleJson0);
Console.WriteLine($"{ex1.GetType().Name}: {ex1}");
Console.WriteLine($"ex1.ToStringJson(): {ex1.ToStringJson()}");
Console.WriteLine($"ex1.ToString2(): {ex1.ToString2()}");
if (app.Environment.IsDevelopment())
{
// 여기 부분
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.Development.json", optional: false, reloadOnChange: true)
.Build();
}
이렇게 입력한 후 로그를 남길 컨트롤러로 돌아갑니다.
이후 부터는 사실 이미 설치되어있는 ILogger를 이용하면 되는데요. 그래도 혹시 처음 접하시는분들을 위해 방법을 남깁니다.
[Route("[controller]")]
public class HomeController : ControllerBase
{
private readonly IConfiguration _configuration;
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger, IConfiguration configuration)
{
_logger = logger;
_configuration = configuration;
}
}
이와 같이 private readonly + 생성자의 변수를 입력하면 ILogger에 대한 의존성 주입이 완료되며,
ILogger로 작성한 로그가 Serilog 라이브러리에서 파일이나 콘솔에 남길 수 있게 됩니다.
각각 메소드에
_logger.LogVerbose();
_logger.LogDebug();
_logger.LogInformation();
_logger.LogWarning();
_logger.LogFatal();
_logger.LogError();
등과 같이 사용하실 수 있습니다. 괄호 내에는 string 변수를 입력하면 되며, 서식 문자열 등은 앞에 Serilog 게시글을 확인하시기 바랍니다.
다음은 main() 또는 program.cs에 작성해야하는 부분이다. using Serilog; 하나면 Serilog.Sinks~류 들을 별도로 설치했다고해서 using도 또 써줘야하는건 아니다.
아래 소스는 위에서 만든 클래스를 선언하여 logFIlePath를 담을뿐 실질적으로Log.Logger 부분만 봐도 무방하다.
using Serilog;
LogHandler logHandler = new();
Log.Logger = new LoggerConfiguration()
//.WriteTo.Console() // Serilog.Sinks.Console를 설치한 경우 사용가능
.WriteTo.File( // Serilog.Sinks.File를 설치한 경우 사용가능
logHandler.logFilePath, // 로그를 지정할 파일 경로
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: null,
fileSizeLimitBytes: 50 * 1024 * 1024,
rollOnFileSizeLimit: true,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level}] {Message}{NewLine}{Exception}")
.MinimumLevel.Information() // 로그 최저레벨을 설정한다.
// .MinimumLevel.Is(Serilog.Events.LogEventLevel.Information) // 바로 위 .MinimumLevel.Information()를 제거하고 이 방법을 사용하면 설정 등으로 가져와서 최저레벨을 지정할 수도 있다.
.CreateLogger();
WriteTo.Console() 소스에 주석으로 적혀있기 때문에 패스하고
WriteTo.File()에는 여러가지 변수가 올 수 있는데
1. rollingInterval은 새로 파일을 만드는 기간 또는 주기를 말하는 것이다. 위 소스에는 RollingInterval.Day이기 때문에 일이 바뀔때마다 새로운 날짜의 파일이 생성된다.
2. retainedFileCountLimit은 로그파일 갯수 상한을 정하는 것이다.
필자의 경우는 서버 관리자가 직접 지울만큼 쌓이기 전까지는 보관하는 것을 선호하기 때문에 null을 입력했다. 그러나 하드용량이 매우적은 컴퓨터나 서버에서 사용한다면 제한을 두는 것이 좋다.
3. fileSizeLimitBytes는 로그파일의 최대크기를 정해놓는 것으로 byte단위로 입력하면 된다. 필자의 짧은 경험상으로는 50mb가 넘어가면 키는데 좀 딜레이가 생기는 것 같아서 저렇게 작성했다.
4. rollOnFileSizeLimit: 은 3번에서 지정한 로그파일의 용량 한계가 넘어가면 어떻게 할지를 결정하는 것이다. true면 다음 파일을 생성하고 false면 로그파일의 용량이 한계에 다다르면 더이상 파일을 작성하지 않는다. 따라서 근방의 로그가 작성되지 않길 바랄리가 없기 떄문에 true를 해야만 한다.
5. outputTemplate: 은 찍히는 로그의 모양을 나타낸 것이다. 미세한 시간차이를 확인하고 싶은게 아니라면 fff를 빼도 좋다. 시간 그 부분 제외하고는 딱히 수정해본적이 없기 때문에 필요하다면 Template작성방식을 검색해보아야 할 것이다.
그리고
.MinimumLevel. 은 찍히는 최저로그레벨을 정하는 것이다. 필자가 기억하기로 Verbose, Debug, Information, Waring, Error, Fatal 정도가 있는데 우측으로 갈수록 높은 레벨이다. 따라서 .MinimumLevel.Information()으로 작성하면 Information, Waring, Error, Fatal 는 찍히고 Verbose, Debug는 무시된다. ( .MinimumLevel.Information() - Information()자리에 Verbose()부터 Fatal()까지 모두 올 수 있다)
.MimimumLevel은 Is()라는 함수도 갖고있는데 변수로 Serilog.Events.LogEventLevel 안에있는 프로퍼티들을 사용할 수 있다. 위의 .MinimumLevel.과 동일한 기능이다. 그러나 Is()가 매력적인 것은 개발시 미리 지정이 아니라 실행시 어떤 로직에 따라서 로그 최저레벨을 정할 수 있다는 점이다.
예를 들면 .MinimumLevel.Is(WhatIWant()) 로 두고 public WhatIWant()의 리턴값으로 Serilog.Events.LogEventLevel.Warning 등을 사용하여 결정할 수있다.