<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>codestyle &amp;mdash; tnpl.me</title>
    <link>https://tnpl.me/tag:codestyle</link>
    <description></description>
    <pubDate>Sun, 26 Apr 2026 14:38:50 +0000</pubDate>
    <item>
      <title>Config vs Setting vs Feature flag</title>
      <link>https://tnpl.me/config-vs-setting-vs-feature-flag</link>
      <description>&lt;![CDATA[Config&#xA;&#xA;Config needs restart to be updated, static, stored in file, override by env variable, small number of config&#xA;&#xA;Example: DB config, server listen port, remote host location&#xA;&#xA;Feature flag&#xA;&#xA;Feature flag and settings can be changed at runtime without restart or down time. Both are similar but I want to draw a bold line separate them.&#xA;&#xA;Feature flag is temporary, has to be clean up or removed someday in the future. It is about roll out, release, use context to decide who, when, where, which version to enable which can be apply to users partially, or globally. It is usually controlled by engineers, or technical operation team.&#xA;&#xA;Setting&#xA;&#xA;Setting is permanently. It has been designed to be in the code without creating technical debt.&#xA;&#xA;It likes a feature which has a real user use case for example to adjust behavior of the system. &#xA;&#xA;It should be safe to be uses, changes, enables, disables in numerous times. It should be test throughout.&#xA;&#xA;Any feature flags that has been rolled out and stable, can be transitioned to setting such as kill switch.&#xA;&#xA;Example: Site title, User role &amp; Permission, Look &amp; feel, Content to be shown&#xA;&#xA;Below is a comparison table:&#xA;&#xA;|                        &#x9;| Config                 &#x9;| Feature flag      &#x9;| Setting                        &#x9;|&#xA;|------------------------&#x9;|------------------------&#x9;|-------------------&#x9;|--------------------------------&#x9;|&#xA;| When to changes        &#x9;| Before start           &#x9;| Runtime           &#x9;| Runtime                        &#x9;|&#xA;| Lifetime               &#x9;| Temporary/Permanently  &#x9;| Temporary         &#x9;| Risky, moderate tested         &#x9;|&#xA;| Safety                 &#x9;| Very safe, well tested &#x9;| Permanently       &#x9;| Safe, well tested              &#x9;|&#xA;| User                   &#x9;| Engineer/Operator      &#x9;| Engineer/Operator &#x9;| Normal user                    &#x9;|&#xA;| Affects                &#x9;| Global/All users       &#x9;| Partial/Global    &#x9;| Global/Depends on each feature &#x9;|&#xA;| Tech Debt              &#x9;| Low                    &#x9;| High              &#x9;| Medium                         &#x9;|&#xA;| Amount of configurable &#x9;| Low                    &#x9;| High              &#x9;| Medium                         &#x9;|&#xA;| Rate of changes        &#x9;| Rarely                 &#x9;| Occationally      &#x9;| On demand                      &#x9;|&#xA;&#xA;codestyle]]&gt;</description>
      <content:encoded><![CDATA[<h2 id="config">Config</h2>

<p>Config needs restart to be updated, static, stored in file, override by env variable, small number of config</p>

<p>Example: DB config, server listen port, remote host location</p>

<h2 id="feature-flag">Feature flag</h2>

<p>Feature flag and settings can be changed at runtime without restart or down time. Both are similar but I want to draw a bold line separate them.</p>

<p>Feature flag is temporary, has to be clean up or removed someday in the future. It is about roll out, release, use context to decide who, when, where, which version to enable which can be apply to users partially, or globally. It is usually controlled by engineers, or technical operation team.</p>

<h2 id="setting">Setting</h2>

<p>Setting is permanently. It has been designed to be in the code without creating technical debt.</p>

<p>It likes a feature which has a real user use case for example to adjust behavior of the system.</p>

<p>It should be safe to be uses, changes, enables, disables in numerous times. It should be test throughout.</p>

<p>Any feature flags that has been rolled out and stable, can be transitioned to setting such as kill switch.</p>

<p>Example: Site title, User role &amp; Permission, Look &amp; feel, Content to be shown</p>

<p>Below is a comparison table:</p>

<table>
<thead>
<tr>
<th></th>
<th>Config</th>
<th>Feature flag</th>
<th>Setting</th>
</tr>
</thead>

<tbody>
<tr>
<td>When to changes</td>
<td>Before start</td>
<td>Runtime</td>
<td>Runtime</td>
</tr>

<tr>
<td>Lifetime</td>
<td>Temporary/Permanently</td>
<td>Temporary</td>
<td>Risky, moderate tested</td>
</tr>

<tr>
<td>Safety</td>
<td>Very safe, well tested</td>
<td>Permanently</td>
<td>Safe, well tested</td>
</tr>

<tr>
<td>User</td>
<td>Engineer/Operator</td>
<td>Engineer/Operator</td>
<td>Normal user</td>
</tr>

<tr>
<td>Affects</td>
<td>Global/All users</td>
<td>Partial/Global</td>
<td>Global/Depends on each feature</td>
</tr>

<tr>
<td>Tech Debt</td>
<td>Low</td>
<td>High</td>
<td>Medium</td>
</tr>

<tr>
<td>Amount of configurable</td>
<td>Low</td>
<td>High</td>
<td>Medium</td>
</tr>

<tr>
<td>Rate of changes</td>
<td>Rarely</td>
<td>Occationally</td>
<td>On demand</td>
</tr>
</tbody>
</table>

<p><a href="https://tnpl.me/tag:codestyle" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">codestyle</span></a></p>
]]></content:encoded>
      <guid>https://tnpl.me/config-vs-setting-vs-feature-flag</guid>
      <pubDate>Sat, 04 Dec 2021 04:32:42 +0000</pubDate>
    </item>
    <item>
      <title>Where to logs</title>
      <link>https://tnpl.me/where-to-logs</link>
      <description>&lt;![CDATA[Related to Opinionated guide on how to write a log&#xA;&#xA;I would like to write in more detail about where to put log, the benefits, disadvantages, and how to mitigate that.&#xA;&#xA;I want to put a context that we&#39;re developing web services, whether public access or internal use. No matter what protocol is used, HTTP with JSON, gRPC, Thrift, etc. I think my guide may adapt to all of these kinds of technology.&#xA;&#xA;So, where to put logs?&#xA;&#xA;!--more--&#xA;&#xA;1. Incoming requests - aka. access log&#xA;&#xA;Usually, there are two types of requests: read and write. You can write a middleware, interceptor, AOP, or whatever is suitable for your situation and log all necessary data such as HTTP request data, response status, process time, caller information, controller and handler name, etc.&#xA;&#xA;This is very useful for many of situations:&#xA;&#xA;To monitor anomaly of 4xx or 5xx HTTP status&#xA;Investigates on which endpoint is slow for which user&#xA;Who perform what action and when&#xA;&#xA;Someone might take it to a very extreme by constantly logging the entire request and response body. Well, it depends on context and company, but I suggest logging those only if there is a serious error such as HTTP 500 so that it won’t violate user privacy, reduce security risk, and save the cost.&#xA;&#xA;2. At the end of handlers that are not read-only&#xA;&#xA;You should put a log to record an event (what happened) at the end of every handler that is not read-only, such as POST, PUT, PATCH, DELETE, or depending on the RPC technology you are using.&#xA;&#xA;I don’t suggest putting it on read-only handlers because most of the systems are very read-heavy, and you already have an access log from above, so it doesn’t add much value to do that on all handlers.&#xA;&#xA;What should be logged on those non-read-only handlers?&#xA;&#xA;Who performs the action&#xA;Name of the event, this should be unique across all of your systems&#xA;Request or data of the action&#xA;Result or data of what have been changed&#xA;Any metrics, numerical values&#xA;&#xA;In order to log so much of those information, structured log such as JSON is a must.&#xA;&#xA;The benefit of doing this is building real-time business metrics to show to all stakeholders. It is pretty exciting to put data, visualize, and see the impact of what you did in real-time.&#xA;&#xA;3. When handlers got an error or exception&#xA;&#xA;Access log with response status is not enough for investigation when there is an error or exception on a request. Handling errors and putting a log in each handler is very helpful to know what&#39;s wrong with that request.&#xA;&#xA;You should log an error message, stack trace, why the error happens, on which block of code. Don&#39;t write a generic error log or catch a generic exception because you won&#39;t get enough detail to understand the problem.&#xA;&#xA;4. When the system calls to external dependencies&#xA;&#xA;In a microservice architecture, it is common that your system has to invoke an API call to other services. You may remember that there are 2 types of requests: read and write. If you send a request to get the data back without any side effects, it is a read, or you may call a query request.&#xA;&#xA;As your system is a client who calls to another service, I suggest you write a log for all requests being sent out from your service. Again, the information to be logged is quite the same as the access log.&#xA;&#xA;There are disadvantages to logging every call, especially for the query request type. The most significant drawback is overhead. Around 80% of requests are query requests; logging all of them will cost you the storage, compute power, and increased response time. In this case, metrics are a better solution as you can count the number of requests, duration histogram.&#xA;&#xA;By the way, if a query request has got an error, I still suggest logging it for investigation. You may also apply a log sampling or rate limit to mitigate the log flood problem in case of 100% of requests are errors.&#xA;&#xA;5. After the system did something important&#xA;&#xA;Even though you had event logging at the end of handlers, there is a missing gap here such as a cron job, watchdog, housekeeping. Those are still not logged and in many cases, it is useful to know what the system does so you can correlate the event in case of something bad happened.&#xA;&#xA;I had an experience on a system where cron jobs are running in the same process as the web server. The application was deployed to many physical servers. Once it is time to run a cron, every server triggers a job simultaneously, which causes a sudden spike in system resources and database.&#xA;&#xA;I&#39;m lucky enough that once I take a look at the centralized log system around that time, it is so obvious that the cron job is a problem.&#xA;&#xA;Here&#39;s a list of what should be log&#xA;&#xA;When a background job or cron job was started and finished&#xA;Events related to database connection such as connection lost, failover&#xA;Garbage collection event&#xA;It&#39;s setting up something with other systems, e.g., declaring queue, registering to service discovery, configuration updates.&#xA;&#xA;6. When you want to record what happened&#xA;&#xA;You can log anywhere in the code, so don&#39;t limit yourself to logging only inside handlers. I usually log about security information, such as someone trying to login using the wrong password, requesting an OTP, delete a record without permission.&#xA;&#xA;You can also log for audit purposes: database record with before &amp; after, login success, email verified, etc.&#xA;&#xA;This might sound a bit duplicate with No 2. but it is not. As a single request can perform many tasks, the log at the end of the handler will record an overall event. But this kind of log is more granular, so it records what happened within a single request.&#xA;&#xA;Closing&#xA;&#xA;I found that logging is more helpful than metrics when you need very detailed information, but it comes with writing, transit, processing, and storing costs. For a system under heavy load, you should reduce the number of logs by using metrics or sampling the logs. But for non-read-only requests, you should always write logs as you might get the benefit when investigating the problems and real-time business metrics for monitoring.&#xA;&#xA;These guidelines on where to logs are what I think you should always do. By the way, feel free to put any log in any log level on the locations that it might be helpful for you.&#xA;&#xA;codestyle]]&gt;</description>
      <content:encoded><![CDATA[<p>Related to <a href="https://tnpl.me/opinionated-guide-on-how-to-write-a-log" rel="nofollow"><strong>Opinionated guide on how to write a log</strong></a></p>

<p>I would like to write in more detail about where to put log, the benefits, disadvantages, and how to mitigate that.</p>

<p>I want to put a context that we&#39;re developing web services, whether public access or internal use. No matter what protocol is used, HTTP with JSON, gRPC, Thrift, etc. I think my guide may adapt to all of these kinds of technology.</p>

<p>So, where to put logs?</p>



<h3 id="1-incoming-requests-aka-access-log">1. Incoming requests – aka. access log</h3>

<p>Usually, there are two types of requests: read and write. You can write a middleware, interceptor, AOP, or whatever is suitable for your situation and log all necessary data such as HTTP request data, response status, process time, caller information, controller and handler name, etc.</p>

<p>This is very useful for many of situations:</p>
<ul><li>To monitor anomaly of 4xx or 5xx HTTP status</li>
<li>Investigates on which endpoint is slow for which user</li>
<li>Who perform what action and when</li></ul>

<p>Someone might take it to a very extreme by constantly logging the entire request and response body. Well, it depends on context and company, but I suggest logging those only if there is a serious error such as HTTP 500 so that it won’t violate user privacy, reduce security risk, and save the cost.</p>

<h3 id="2-at-the-end-of-handlers-that-are-not-read-only">2. At the end of handlers that are not read-only</h3>

<p>You should put a log to record an event (what happened) at the end of every handler that is not read-only, such as POST, PUT, PATCH, DELETE, or depending on the RPC technology you are using.</p>

<p>I don’t suggest putting it on read-only handlers because most of the systems are very read-heavy, and you already have an access log from above, so it doesn’t add much value to do that on all handlers.</p>

<p>What should be logged on those non-read-only handlers?</p>
<ul><li>Who performs the action</li>
<li>Name of the event, this should be unique across all of your systems</li>
<li>Request or data of the action</li>
<li>Result or data of what have been changed</li>
<li>Any metrics, numerical values</li></ul>

<p>In order to log so much of those information, structured log such as JSON is a must.</p>

<p>The benefit of doing this is building real-time business metrics to show to all stakeholders. It is pretty exciting to put data, visualize, and see the impact of what you did in real-time.</p>

<h3 id="3-when-handlers-got-an-error-or-exception">3. When handlers got an error or exception</h3>

<p>Access log with response status is not enough for investigation when there is an error or exception on a request. Handling errors and putting a log in each handler is very helpful to know what&#39;s wrong with that request.</p>

<p>You should log an error message, stack trace, why the error happens, on which block of code. Don&#39;t write a generic error log or catch a generic exception because you won&#39;t get enough detail to understand the problem.</p>

<h3 id="4-when-the-system-calls-to-external-dependencies">4. When the system calls to external dependencies</h3>

<p>In a microservice architecture, it is common that your system has to invoke an API call to other services. You may remember that there are 2 types of requests: read and write. If you send a request to get the data back without any side effects, it is a read, or you may call a query request.</p>

<p>As your system is a client who calls to another service, I suggest you write a log for all requests being sent out from your service. Again, the information to be logged is quite the same as the access log.</p>

<p>There are disadvantages to logging every call, especially for the query request type. The most significant drawback is overhead. Around 80% of requests are query requests; logging all of them will cost you the storage, compute power, and increased response time. In this case, metrics are a better solution as you can count the number of requests, duration histogram.</p>

<p>By the way, if a query request has got an error, I still suggest logging it for investigation. You may also apply a log sampling or rate limit to mitigate the log flood problem in case of 100% of requests are errors.</p>

<h3 id="5-after-the-system-did-something-important">5. After the system did something important</h3>

<p>Even though you had event logging at the end of handlers, there is a missing gap here such as a cron job, watchdog, housekeeping. Those are still not logged and in many cases, it is useful to know what the system does so you can correlate the event in case of something bad happened.</p>

<p>I had an experience on a system where cron jobs are running in the same process as the web server. The application was deployed to many physical servers. Once it is time to run a cron, every server triggers a job simultaneously, which causes a sudden spike in system resources and database.</p>

<p>I&#39;m lucky enough that once I take a look at the centralized log system around that time, it is so obvious that the cron job is a problem.</p>

<p>Here&#39;s a list of what should be log</p>
<ul><li>When a background job or cron job was started and finished</li>
<li>Events related to database connection such as connection lost, failover</li>
<li>Garbage collection event</li>
<li>It&#39;s setting up something with other systems, e.g., declaring queue, registering to service discovery, configuration updates.</li></ul>

<h3 id="6-when-you-want-to-record-what-happened">6. When you want to record what happened</h3>

<p>You can log anywhere in the code, so don&#39;t limit yourself to logging only inside handlers. I usually log about security information, such as someone trying to login using the wrong password, requesting an OTP, delete a record without permission.</p>

<p>You can also log for audit purposes: database record with before &amp; after, login success, email verified, etc.</p>

<p>This might sound a bit duplicate with No 2. but it is not. As a single request can perform many tasks, the log at the end of the handler will record an overall event. But this kind of log is more granular, so it records what happened within a single request.</p>

<h2 id="closing">Closing</h2>

<p>I found that logging is more helpful than metrics when you need very detailed information, but it comes with writing, transit, processing, and storing costs. For a system under heavy load, you should reduce the number of logs by using metrics or sampling the logs. But for non-read-only requests, you should always write logs as you might get the benefit when investigating the problems and real-time business metrics for monitoring.</p>

<p>These guidelines on where to logs are what I think you should always do. By the way, feel free to put any log in any log level on the locations that it might be helpful for you.</p>

<p><a href="https://tnpl.me/tag:codestyle" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">codestyle</span></a></p>
]]></content:encoded>
      <guid>https://tnpl.me/where-to-logs</guid>
      <pubDate>Sat, 20 Nov 2021 04:27:58 +0000</pubDate>
    </item>
    <item>
      <title>Opinionated guide on how to write a log</title>
      <link>https://tnpl.me/opinionated-guide-on-how-to-write-a-log</link>
      <description>&lt;![CDATA[เคยลองเปิด log message ที่ตัวเองเขียนไหมครับ? รู้สึกอย่างไรกับ log เหล่านั้น? มีประโยชน์ตามที่ต้องการหรือเปล่า? และเมื่อระบบมีปัญหาลองเปิด log message อ่านดูอีกครั้ง คิดว่า log เหล่านั้นให้ประโยชน์กับเราขนาดไหน?&#xA;&#xA;ผมลองใช้เทคนิคเหล่านี้ในการเขียน log มันทำให้ log มีประโยชน์มากขึ้น&#xA;&#xA;1. Use structured log&#xA;&#xA;ส่วนตัวผมแนะนำ log เป็น JSON format เนื่องจากสามารถใส่ข้อมูลชนิดต่างๆ ได้หลากหลาย มีโครงสร้างชัดเจน หากจะใช้ structure แบบอื่นก็ทำได้ ขอให้ parse ข้อมูลใน log ได้ง่าย แต่สิ่งที่ไม่ควรทำคือ log แบบไม่มีโครงสร้างที่ชัดเจน&#xA;&#xA;ตัวอย่าง:&#xA;&#xA;{&#34;timestamp&#34;:&#34;2021-04-21T10.30.15.412Z&#34;,&#34;level&#34;:&#34;INFO&#34;,&#34;message&#34;:&#34;Order created&#34;,&#34;orderId&#34;:&#34;ORD1&#34;,&#34;userId&#34;:&#34;U1&#34;,&#34;subsystem&#34;:&#34;SvcName.Package.Class.Method&#34;}&#xA;&#xA;!--more--&#xA;&#xA;2. Log with context&#xA;&#xA;ในระบบ centralized log จะมี log จำนวนมากจากระบบต่างๆ ถึงแม้ว่าจะ filter แยกตาม service ได้ก็ตาม แต่ 1 service อาจจะ serve หลาย request พร้อมกัน ส่งผลให้เราอ่าน log รอบๆ ได้ยาก&#xA;&#xA;ดังนั้นให้ log ข้อมูลประกอบออกมาให้ครบถ้วนใน 1 line เราควรจะตัดสินใจ/identify ปัญหาได้โดยไม่ต้องอาศัย log อื่นๆ ประกอบ รวมถึงเขียน message อธิบายให้ครบถ้วน อย่ากังวลเรื่องขนาดของ 1 log line ในช่วงแรกเพราะเมื่อเทียบดูแล้วการเสียพื้นที่เก็บ log ปริมาณมากที่ใช้ประโยชน์ไม่ได้จริงนั้นแย่กว่าการเก็บ log ขนาดใหญ่เพียง 1 บรรทัดที่มีประโยชน์&#xA;&#xA;ตัวอย่าง:&#xA;&#xA;ถ้าเป็นระบบ order ควรใส่ userId, orderId, action ที่ทำ&#xA;ถ้าเป็นระบบ login ควรใส่ attempt, ip, sessionId, พยายาม login เป็น userId อะไร, success ไหม&#xA;ถ้าเป็น http access log ควรใส่ client ip, http method, full path, domain, response code ฯลฯ&#xA;&#xA;3. Prefer static log message&#xA;&#xA;ผมเคยเขียน log message ที่ human friendly มากๆ เช่น&#xA;&#xA;Order id 1001 cannot transition from CREATED to PROCESSING state due to order expired&#xA;&#xA;ปัญหาของ log message นี้คือ&#xA;&#xA;Search ไม่ได้&#xA;Parse data ออกมาไม่ได้&#xA;เสีย CPU time เพื่อ format log message&#xA;&#xA;ผมรู้สึกว่า static log message นั้นเรียบง่ายและไร้ปัญหาด้านบน และไม่ต้องเสียเวลาเช็ค grammar อีกด้วย&#xA;&#xA;message ใหม่หน้าตาเป็นแบบนี้&#xA;&#xA;Order state transition error - expired&#xA;&#xA;ส่วนข้อมูล order id, state before, state after, expired time ก็ใช้ structured log ร่วมกับ context&#xA;&#xA;4. ใส่ Subsystem ด้วย&#xA;&#xA;เมื่อเริ่มใช้ static log message ที่กระชับ จะมีโอกาสที่ code มากกว่า 2 จุดจะใช้ message เดียวกัน ทำให้เราไม่รู้ว่า log นี้ถูกเขียนออกมาจากจุดไหนใน code&#xA;&#xA;ดังนั้น ควรใส่ข้อมูล subsystem เข้าไปใน log ด้วยเพื่อให้รู้ว่า log ออกมาจากจุดไหนใน code ข้อมูลที่ควรใส่มีดังนี้ ตามลำดับ&#xA;&#xA;Hostname&#xA;Application name&#xA;Module name&#xA;Package name&#xA;Class name&#xA;Method name&#xA;&#xA;เราสามารถตัดข้อที่มีข้อมูลอยู่แล้วไปได้ เช่น Hostname, Application name ถ้าเราใช้ filebeat + logstash + docker + kubernetes จะมีข้อมูลนี้ส่งไปให้อยู่แล้ว&#xA;&#xA;log framework เช่น log4j จะใส่ชื่อ Package กับ Class ให้อยู่แล้ว ดังนั้นไม่จำเป็นต้องใส่ แต่ถ้า log framework ที่ใช้ไม่ได้ใส่ให้เราก็ต้องใส่เอง&#xA;&#xA;ถ้าหากใน class หรือ package มี log message ซ้ำกัน ก็ควรใส่ method name เข้าไปด้วย (แนะนำให้ใส่ตลอดถึงแม้ว่า message จะไม่ซ้ำ)&#xA;&#xA;ตัวอย่าง subsystem com.example.app.service.OrderService.updateState ถ้าเห็นว่ายาวไปก็อาจจะตัดเหลือแค่ c.e.a.s.OrderService.updateState หรือ OrderService.updateState แต่ต้องระวังว่าถ้าใน code มี class OrderService อยู่คนละ package ก็จะมีปัญหาได้&#xA;&#xA;5. ห้ามใช้ Log level ผิด&#xA;&#xA;ปกติ log level จะมี FATAL, ERROR, WARN, INFO, DEBUG, และ TRACE เรียงตามความ critical/serious จากมากไปน้อยตามลำดับ&#xA;&#xA;การห้ามใช้ผิดคืออย่าใช้ level ที่ critical กับ log ที่ไม่ critical เช่น ถ้าอยากจะ log ดูข้อมูลเฉยๆ ห้ามใช้ FATAL, ERROR, และ WARN เป็นต้น แต่จะใช้ INFO, DEBUG, หรือ TRACE ผมไม่มีความเห็นเป็นพิเศษ&#xA;&#xA;6. ใช้ Centralized log system&#xA;&#xA;แนะนำให้ใช้ ELK stack โดยสามารถ parse JSON log ใน logstash ได้เลย ส่วน log ทั้งหมดสามารถ search ผ่าน ElasticSearch และ Kibana ได้ง่าย สามารถ filter แยกตาม field ได้ เช่น hostname, application name, subsystem หรือจะทำ full text search ผ่าน message ก็ได้&#xA;&#xA;7. Log ลงไฟล์หรือ stdout/stderr เท่านั้น&#xA;&#xA;by default ควร log ลงไฟล์ ยกเว้นถ้าใช้ docker ให้ log ลง stdout/stderr ได้ (ทำงานเร็ว, overhead ต่ำ)&#xA;&#xA;สาเหตุ&#xA;&#xA;ถ้า ship log ผ่าน TCP จะเกิดปัญหา app freeze เมื่อ centralized log มีปัญหาเพราะ TCP buffer ฝั่งผู้ส่ง log เต็ม&#xA;ถ้า ship log ผ่าน UDP จะเกิดปัญหา log message lost เพราะขนาดใหญ่เกิน 1 UDP packet&#xA;ถ้าไม่ใช้ docker แล้วเขียนลง stdout/stderr จะเกิดปัญหา app ทำงานช้า เพราะ stdout/stderr ไม่ใช่ buffered IO และ flush ตลอด&#xA;&#xA;ปัญหาเหล่านี้แก้ไขได้แต่ต้องอาศัยความระวังเป็นพิเศษ และเป็น common mistake ที่ต้องเจอ ดังนั้นเลือกทางที่ปลอดภัยตั้งแต่แรกดีกว่า ข้อควรระวังอีกเรื่องคือเมื่อเขียนลง file หรือ docker log ให้ระวัง disk เต็ม ควรจะ rotate log ให้เหมาะสมด้วย&#xA;&#xA;8. Mask sensitive data ด้วยเสมอ&#xA;&#xA;สมมติทำระบบ login ด้วย phone number เราไม่ควร log phone number ทั้งหมดออกมา เสี่ยงต่อการถูกขโมยข้อมูลและละเมิด privacy&#xA;&#xA;เราควร mask data ตามความเหมาะสมด้วย เช่น เบอร์โทร จากเดิม 029876543 ให้เหลือ 02xxxx543 อย่างนี้จะยังช่วยให้เรารู้ข้อมูลบางส่วนและค้นหา log ได้อยู่ แต่ข้อมูลบางประเภทควรจะ mask ทั้งหมด เช่น ข้อมูลรหัสผ่าน, authentication token, session id เป็นต้น&#xA;&#xA;สุดท้าย&#xA;&#xA;ระบบ log ต่างๆ เหล่านี้ต้องใช้เงินจำนวนมากในการ operate ระบบ ไม่ว่าจะเป็น log storage, log parser, log query service, log shipper, และ data transfer&#xA;&#xA;แต่ระบบ log ที่ดีรวมถึงการเขียน log ที่ดีจะช่วยให้ชีวิตของ engineer ดีขึ้นเป็นอย่างมาก มี productivity สูงขึ้น และนำ log data ไปต่อยอดในเรื่องต่างๆ ได้มหาศาลในอนาคต&#xA;&#xA;codestyle]]&gt;</description>
      <content:encoded><![CDATA[<p>เคยลองเปิด log message ที่ตัวเองเขียนไหมครับ? รู้สึกอย่างไรกับ log เหล่านั้น? มีประโยชน์ตามที่ต้องการหรือเปล่า? และเมื่อระบบมีปัญหาลองเปิด log message อ่านดูอีกครั้ง คิดว่า log เหล่านั้นให้ประโยชน์กับเราขนาดไหน?</p>

<p>ผมลองใช้เทคนิคเหล่านี้ในการเขียน log มันทำให้ log มีประโยชน์มากขึ้น</p>

<h3 id="1-use-structured-log">1. Use structured log</h3>

<p>ส่วนตัวผมแนะนำ log เป็น JSON format เนื่องจากสามารถใส่ข้อมูลชนิดต่างๆ ได้หลากหลาย มีโครงสร้างชัดเจน หากจะใช้ structure แบบอื่นก็ทำได้ ขอให้ parse ข้อมูลใน log ได้ง่าย แต่สิ่งที่ไม่ควรทำคือ log แบบไม่มีโครงสร้างที่ชัดเจน</p>

<p>ตัวอย่าง:</p>

<pre><code class="language-json">{&#34;timestamp&#34;:&#34;2021-04-21T10.30.15.412Z&#34;,&#34;level&#34;:&#34;INFO&#34;,&#34;message&#34;:&#34;Order created&#34;,&#34;orderId&#34;:&#34;ORD1&#34;,&#34;userId&#34;:&#34;U1&#34;,&#34;subsystem&#34;:&#34;SvcName.Package.Class.Method&#34;}
</code></pre>



<h3 id="2-log-with-context">2. Log with context</h3>

<p>ในระบบ centralized log จะมี log จำนวนมากจากระบบต่างๆ ถึงแม้ว่าจะ filter แยกตาม service ได้ก็ตาม แต่ 1 service อาจจะ serve หลาย request พร้อมกัน ส่งผลให้เราอ่าน log รอบๆ ได้ยาก</p>

<p>ดังนั้นให้ log ข้อมูลประกอบออกมาให้ครบถ้วนใน 1 line เราควรจะตัดสินใจ/identify ปัญหาได้โดยไม่ต้องอาศัย log อื่นๆ ประกอบ รวมถึงเขียน message อธิบายให้ครบถ้วน อย่ากังวลเรื่องขนาดของ 1 log line ในช่วงแรกเพราะเมื่อเทียบดูแล้วการเสียพื้นที่เก็บ log ปริมาณมากที่ใช้ประโยชน์ไม่ได้จริงนั้นแย่กว่าการเก็บ log ขนาดใหญ่เพียง 1 บรรทัดที่มีประโยชน์</p>

<p>ตัวอย่าง:</p>
<ul><li>ถ้าเป็นระบบ order ควรใส่ userId, orderId, action ที่ทำ</li>
<li>ถ้าเป็นระบบ login ควรใส่ attempt, ip, sessionId, พยายาม login เป็น userId อะไร, success ไหม</li>
<li>ถ้าเป็น http access log ควรใส่ client ip, http method, full path, domain, response code ฯลฯ</li></ul>

<h3 id="3-prefer-static-log-message">3. Prefer static log message</h3>

<p>ผมเคยเขียน log message ที่ human friendly มากๆ เช่น</p>

<pre><code>Order id 1001 cannot transition from CREATED to PROCESSING state due to order expired
</code></pre>

<p>ปัญหาของ log message นี้คือ</p>
<ol><li>Search ไม่ได้</li>
<li>Parse data ออกมาไม่ได้</li>
<li>เสีย CPU time เพื่อ format log message</li></ol>

<p>ผมรู้สึกว่า static log message นั้นเรียบง่ายและไร้ปัญหาด้านบน และไม่ต้องเสียเวลาเช็ค grammar อีกด้วย</p>

<p>message ใหม่หน้าตาเป็นแบบนี้</p>

<pre><code>Order state transition error - expired
</code></pre>

<p>ส่วนข้อมูล order id, state before, state after, expired time ก็ใช้ structured log ร่วมกับ context</p>

<h3 id="4-ใส-subsystem-ด-วย">4. ใส่ Subsystem ด้วย</h3>

<p>เมื่อเริ่มใช้ static log message ที่กระชับ จะมีโอกาสที่ code มากกว่า 2 จุดจะใช้ message เดียวกัน ทำให้เราไม่รู้ว่า log นี้ถูกเขียนออกมาจากจุดไหนใน code</p>

<p>ดังนั้น ควรใส่ข้อมูล subsystem เข้าไปใน log ด้วยเพื่อให้รู้ว่า log ออกมาจากจุดไหนใน code ข้อมูลที่ควรใส่มีดังนี้ ตามลำดับ</p>
<ol><li>Hostname</li>
<li>Application name</li>
<li>Module name</li>
<li>Package name</li>
<li>Class name</li>
<li>Method name</li></ol>

<p>เราสามารถตัดข้อที่มีข้อมูลอยู่แล้วไปได้ เช่น Hostname, Application name ถ้าเราใช้ filebeat + logstash + docker + kubernetes จะมีข้อมูลนี้ส่งไปให้อยู่แล้ว</p>

<p>log framework เช่น log4j จะใส่ชื่อ Package กับ Class ให้อยู่แล้ว ดังนั้นไม่จำเป็นต้องใส่ แต่ถ้า log framework ที่ใช้ไม่ได้ใส่ให้เราก็ต้องใส่เอง</p>

<p>ถ้าหากใน class หรือ package มี log message ซ้ำกัน ก็ควรใส่ method name เข้าไปด้วย (แนะนำให้ใส่ตลอดถึงแม้ว่า message จะไม่ซ้ำ)</p>

<p>ตัวอย่าง subsystem <code>com.example.app.service.OrderService.updateState</code> ถ้าเห็นว่ายาวไปก็อาจจะตัดเหลือแค่ <code>c.e.a.s.OrderService.updateState</code> หรือ <code>OrderService.updateState</code> แต่ต้องระวังว่าถ้าใน code มี class <code>OrderService</code> อยู่คนละ package ก็จะมีปัญหาได้</p>

<h3 id="5-ห-ามใช-log-level-ผ-ด">5. ห้ามใช้ Log level ผิด</h3>

<p>ปกติ log level จะมี FATAL, ERROR, WARN, INFO, DEBUG, และ TRACE เรียงตามความ critical/serious จากมากไปน้อยตามลำดับ</p>

<p>การห้ามใช้ผิดคืออย่าใช้ level ที่ critical กับ log ที่ไม่ critical เช่น ถ้าอยากจะ log ดูข้อมูลเฉยๆ ห้ามใช้ FATAL, ERROR, และ WARN เป็นต้น แต่จะใช้ INFO, DEBUG, หรือ TRACE ผมไม่มีความเห็นเป็นพิเศษ</p>

<h3 id="6-ใช-centralized-log-system">6. ใช้ Centralized log system</h3>

<p>แนะนำให้ใช้ ELK stack โดยสามารถ parse JSON log ใน logstash ได้เลย ส่วน log ทั้งหมดสามารถ search ผ่าน ElasticSearch และ Kibana ได้ง่าย สามารถ filter แยกตาม field ได้ เช่น hostname, application name, subsystem หรือจะทำ full text search ผ่าน message ก็ได้</p>

<h3 id="7-log-ลงไฟล-หร-อ-stdout-stderr-เท-าน-น">7. Log ลงไฟล์หรือ stdout/stderr เท่านั้น</h3>

<p>by default ควร log ลงไฟล์ ยกเว้นถ้าใช้ docker ให้ log ลง stdout/stderr ได้ (ทำงานเร็ว, overhead ต่ำ)</p>

<p>สาเหตุ</p>
<ul><li>ถ้า ship log ผ่าน TCP จะเกิดปัญหา app freeze เมื่อ centralized log มีปัญหาเพราะ TCP buffer ฝั่งผู้ส่ง log เต็ม</li>
<li>ถ้า ship log ผ่าน UDP จะเกิดปัญหา log message lost เพราะขนาดใหญ่เกิน 1 UDP packet</li>
<li>ถ้าไม่ใช้ docker แล้วเขียนลง stdout/stderr จะเกิดปัญหา app ทำงานช้า เพราะ stdout/stderr ไม่ใช่ buffered IO และ flush ตลอด</li></ul>

<p>ปัญหาเหล่านี้แก้ไขได้แต่ต้องอาศัยความระวังเป็นพิเศษ และเป็น common mistake ที่ต้องเจอ ดังนั้นเลือกทางที่ปลอดภัยตั้งแต่แรกดีกว่า ข้อควรระวังอีกเรื่องคือเมื่อเขียนลง file หรือ docker log ให้ระวัง disk เต็ม ควรจะ rotate log ให้เหมาะสมด้วย</p>

<h3 id="8-mask-sensitive-data-ด-วยเสมอ">8. Mask sensitive data ด้วยเสมอ</h3>

<p>สมมติทำระบบ login ด้วย phone number เราไม่ควร log phone number ทั้งหมดออกมา เสี่ยงต่อการถูกขโมยข้อมูลและละเมิด privacy</p>

<p>เราควร mask data ตามความเหมาะสมด้วย เช่น เบอร์โทร จากเดิม 029876543 ให้เหลือ 02xxxx543 อย่างนี้จะยังช่วยให้เรารู้ข้อมูลบางส่วนและค้นหา log ได้อยู่ แต่ข้อมูลบางประเภทควรจะ mask ทั้งหมด เช่น ข้อมูลรหัสผ่าน, authentication token, session id เป็นต้น</p>

<h1 id="ส-ดท-าย"><strong>สุดท้าย</strong></h1>

<p>ระบบ log ต่างๆ เหล่านี้ต้องใช้เงินจำนวนมากในการ operate ระบบ ไม่ว่าจะเป็น log storage, log parser, log query service, log shipper, และ data transfer</p>

<p>แต่ระบบ log ที่ดีรวมถึงการเขียน log ที่ดีจะช่วยให้ชีวิตของ engineer ดีขึ้นเป็นอย่างมาก มี productivity สูงขึ้น และนำ log data ไปต่อยอดในเรื่องต่างๆ ได้มหาศาลในอนาคต</p>

<p><a href="https://tnpl.me/tag:codestyle" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">codestyle</span></a></p>
]]></content:encoded>
      <guid>https://tnpl.me/opinionated-guide-on-how-to-write-a-log</guid>
      <pubDate>Wed, 21 Apr 2021 04:13:08 +0000</pubDate>
    </item>
  </channel>
</rss>