<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.8.7">Jekyll</generator><link href="https://ohyecloudy.com/ddiary/feed.xml" rel="self" type="application/atom+xml" /><link href="https://ohyecloudy.com/ddiary/" rel="alternate" type="text/html" /><updated>2025-12-06T14:49:40+09:00</updated><id>https://ohyecloudy.com/ddiary/feed.xml</id><title type="html">dev diary, TIL</title><subtitle>톱밥과 Today I Learned</subtitle><author><name>Jongbin Oh</name><email>ohyecloudy@gmail.com</email></author><entry><title type="html">#TIL Linux Swap space(스왑 공간) 설정</title><link href="https://ohyecloudy.com/ddiary/2025/08/09/til-linux-swap-space/" rel="alternate" type="text/html" title="#TIL Linux Swap space(스왑 공간) 설정" /><published>2025-08-09T00:00:00+09:00</published><updated>2025-08-09T00:00:00+09:00</updated><id>https://ohyecloudy.com/ddiary/2025/08/09/til-linux-swap-space</id><content type="html" xml:base="https://ohyecloudy.com/ddiary/2025/08/09/til-linux-swap-space/"><![CDATA[<p>클라우드 서비스에서 저사양 가상 머신 인스턴스를 생성했다면 시스템 스왑 공간을 가장 먼저 확인해야 한다. 시스템 RAM 크기가 작아서 OOM(Out Of Memory) 발생 가능성이 더 높아 시스템이 불안정해지기 때문이다.</p>

<p>아래 명령은 Ubuntu 기준이다.</p>

<p>우선 스왑 공간을 확인해 본다.</p>

<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo swapon --show
</code></pre></div></div>

<p>아무것도 출력되지 않는다면 스왑 공간이 없다는 뜻이다.</p>

<p>스왑 공간으로 사용할 파일을 만든다.</p>

<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo fallocate -l 1G /swapfile
$ sudo chmod 600 /swapfile
</code></pre></div></div>

<p>만든 파일을 스왑 공간으로 설정한다.</p>

<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo mkswap /swapfile

Setting up swapspace version 1, size = 1024 MiB (1073737728 bytes)
no label, UUID=6e965805-2ab9-450f-aed6-577e74089dbf
</code></pre></div></div>

<p>swap on!</p>

<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo swapon /swapfile
</code></pre></div></div>

<p>확인하면 제일 처음과 다르게 스왑 공간이 표시된다</p>

<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo swapon --show

NAME      TYPE  SIZE USED PRIO
/swapfile file 1024M   0B   -2
</code></pre></div></div>

<p>여기까지 하면 현재 세션에만 스왑 공간이 적용된다. 영구적으로 변경해서 재부팅해도 적용되게 설정한다.</p>

<p>혹시 모르니 기존 설정 파일 백업부터 한다.</p>

<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo cp /etc/fstab /etc/fstab.bak
</code></pre></div></div>

<p>스왑 공간 활성화 세팅을 <code class="highlighter-rouge">/etc/fstab</code> 에 추가한다.</p>

<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
</code></pre></div></div>

<!----- Footnotes ----->]]></content><author><name>Jongbin Oh</name><email>ohyecloudy@gmail.com</email></author><category term="til" /><category term="linux" /><category term="memory" /><summary type="html"><![CDATA[클라우드 서비스에서 저사양 가상 머신 인스턴스를 생성했다면 시스템 스왑 공간을 가장 먼저 확인해야 한다. 시스템 RAM 크기가 작아서 OOM(Out Of Memory) 발생 가능성이 더 높아 시스템이 불안정해지기 때문이다.]]></summary></entry><entry><title type="html">#TIL elixir 1.11에 추가된 구조화된 로깅(keyword, map)</title><link href="https://ohyecloudy.com/ddiary/2025/07/26/til-elixir-structured-logging/" rel="alternate" type="text/html" title="#TIL elixir 1.11에 추가된 구조화된 로깅(keyword, map)" /><published>2025-07-26T00:00:00+09:00</published><updated>2025-07-26T00:00:00+09:00</updated><id>https://ohyecloudy.com/ddiary/2025/07/26/til-elixir-structured-logging</id><content type="html" xml:base="https://ohyecloudy.com/ddiary/2025/07/26/til-elixir-structured-logging/"><![CDATA[<p>keyword, map을 변환할 필요없이 로거 인자로 넘길 수 있다. 편해졌다.</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Logger</span><span class="o">.</span><span class="n">info</span><span class="p">([</span><span class="ss">new_user:</span> <span class="n">user_id</span><span class="p">,</span> <span class="ss">account_type:</span> <span class="ss">:admin</span><span class="p">])</span>
<span class="no">Logger</span><span class="o">.</span><span class="n">info</span><span class="p">(%{</span><span class="ss">new_user:</span> <span class="n">user_id</span><span class="p">,</span> <span class="ss">account_type:</span> <span class="ss">:admin</span><span class="p">})</span>
</code></pre></div></div>

<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>11:03:47.330 [info] [new_user: 5, account_type: :admin]
</code></pre></div></div>

<p>섞어 쓸 수 있을까?</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s2">"We have a problem"</span><span class="p">,</span> <span class="p">[</span><span class="ss">error_code:</span> <span class="ss">:pc_load_letter</span><span class="p">])</span>
</code></pre></div></div>

<p>안타깝게도 이런 형식은 안 된다. 두 번째 인자는 metadata로 들어간다.</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Logger</span><span class="o">.</span><span class="n">error</span><span class="p">([</span><span class="ss">msg:</span> <span class="s2">"We have a problem"</span><span class="p">,</span> <span class="ss">error_code:</span> <span class="ss">:pc_load_letter</span><span class="p">])</span>
</code></pre></div></div>

<p>그래서 메시지까지 구조화해서 로깅했다.</p>

<blockquote>
  <p>On the footsteps of v1.10, we have further integrated with Erlang’s new logger by adding four new log levels: notice, critical, alert, and emergency, matching all log levels found in the Syslog standard. The Logger module now supports structured logging by passing maps and keyword lists to its various functions. It is also possible to specify the log level per module, via the Logger.put_module_level/2 function. Log levels per application will be added in future releases.</p>

  <p><a href="https://elixir-lang.org/blog/2020/10/06/elixir-v1-11-0-released/">Elixir v1.11 released - The Elixir programming language - elixir-lang.org</a></p>
</blockquote>

<p>1.11에 추가됐다. 1.19rc가 나온 지금 한참 전에 추가된 피처다. 1.10 버전을 엄청나게 오래 써서 그런지 여기에 멈춰있었다.</p>

<!----- Footnotes ----->]]></content><author><name>Jongbin Oh</name><email>ohyecloudy@gmail.com</email></author><category term="til" /><category term="elixir" /><category term="logger" /><summary type="html"><![CDATA[keyword, map을 변환할 필요없이 로거 인자로 넘길 수 있다. 편해졌다.]]></summary></entry><entry><title type="html">#TIL Linux 유저에게 특정 서비스에 대한 systemctl 프로그램 사용을 허용하기</title><link href="https://ohyecloudy.com/ddiary/2025/07/13/linux-sudoers-systemctl/" rel="alternate" type="text/html" title="#TIL Linux 유저에게 특정 서비스에 대한 systemctl 프로그램 사용을 허용하기" /><published>2025-07-13T00:00:00+09:00</published><updated>2025-07-13T00:00:00+09:00</updated><id>https://ohyecloudy.com/ddiary/2025/07/13/linux-sudoers-systemctl</id><content type="html" xml:base="https://ohyecloudy.com/ddiary/2025/07/13/linux-sudoers-systemctl/"><![CDATA[<p><a href="https://ohyecloudy.com/ddiary/2025/03/22/systemd-journald/">Systemd</a>로 실행을 관리하는 프로그램이 있다. <a href="https://ohyecloudy.com/pnotes/archives/github-actions-arm64-build-deploy/">GitHub actions를 사용해 빌드하고 scp 프로그램으로 배포</a>한다. 복사 전후에 프로세스 중지 실행을 수행해야 한다. systemd에 대한 전체 권한을 주지 않고 특정 디렉터리에 권한을 준 것처럼 특정 프로그램의 시작과 종료만 가능하게 하고 싶다.</p>

<p><code class="highlighter-rouge">/etc/sudoers</code> 파일에 특정 유저가 root 권한을 얻어 실행할 수 있는 동작을 정의할 수 있다. Drop-in을 지원하니 <code class="highlighter-rouge">/etc/sudoers</code> 파일을 직접 수정하지 말고 <code class="highlighter-rouge">/etc/sudoers.d/deployuser-systemctl</code> 처럼 파일을 만든다.</p>

<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo visudo -f /etc/sudoers.d/deployuser-systemctl
</code></pre></div></div>

<p><code class="highlighter-rouge">sudoers</code> 파일 문법 검사를 해주는 <code class="highlighter-rouge">visudo</code> 를 통해 수정한다.</p>

<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>deployuser ALL=NOPASSWD: /usr/bin/systemctl restart myapp, /usr/bin/systemctl start myapp, /usr/bin/systemctl stop myapp
</code></pre></div></div>

<p>이제 <code class="highlighter-rouge">deployuser</code> 가 <code class="highlighter-rouge">myapp</code> 을 비밀번호 입력 없이 sudo systemctl로 컨트롤할 수 있게 했다.</p>

<!----- Footnotes ----->]]></content><author><name>Jongbin Oh</name><email>ohyecloudy@gmail.com</email></author><category term="til" /><category term="linux" /><category term="user" /><category term="sudo" /><category term="systemctl" /><summary type="html"><![CDATA[Systemd로 실행을 관리하는 프로그램이 있다. GitHub actions를 사용해 빌드하고 scp 프로그램으로 배포한다. 복사 전후에 프로세스 중지 실행을 수행해야 한다. systemd에 대한 전체 권한을 주지 않고 특정 디렉터리에 권한을 준 것처럼 특정 프로그램의 시작과 종료만 가능하게 하고 싶다.]]></summary></entry><entry><title type="html">#TIL elixir에서 erlang 파일 로그 핸들러를 사용하기</title><link href="https://ohyecloudy.com/ddiary/2025/06/15/til-elixir-file-logger/" rel="alternate" type="text/html" title="#TIL elixir에서 erlang 파일 로그 핸들러를 사용하기" /><published>2025-06-15T00:00:00+09:00</published><updated>2025-06-15T00:00:00+09:00</updated><id>https://ohyecloudy.com/ddiary/2025/06/15/til-elixir-file-logger</id><content type="html" xml:base="https://ohyecloudy.com/ddiary/2025/06/15/til-elixir-file-logger/"><![CDATA[<p>elixir 1.15에서 erlang logger 와의 통합이 이뤄졌다. 그래서 elixir에서 erlang의 파일 로거 핸들러를 손쉽게 사용할 수 있다. <a href="https://ohyecloudy.com/ddiary/2025/06/01/til-elixir-erlang-logger-handler/">elixir logger backend가 deprecated</a> 돼서 설정을 바꾸는게 귀찮았지만 erlang의 파일 로거를 사용할 수 있다. elixir 스탠다드 라이브러리에 파일 로거가 직접 만들거나 파일 로거 backend 라이브러리를 추가해야 했다. 이렇게 많이 쓰는 걸 왜 추가하지 않나 했는데, 다 계획이 있었구나.</p>

<p>로거의 디폴트 핸들러는 하나만 설치할 수 있다.</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">config</span> <span class="ss">:logger</span><span class="p">,</span> <span class="ss">:default_handler</span><span class="p">,</span>
  <span class="ss">config:</span> <span class="p">[</span>
    <span class="ss">type:</span> <span class="ss">:file</span><span class="p">,</span>
    <span class="ss">file:</span> <span class="sx">~c"logs/app.log"</span><span class="p">,</span>
    <span class="ss">max_no_bytes:</span> <span class="mi">10_000_000</span><span class="p">,</span>
    <span class="ss">max_no_files:</span> <span class="mi">5</span><span class="p">,</span>
    <span class="ss">file_check:</span> <span class="mi">5000</span><span class="p">,</span>
    <span class="ss">filesync_repeat_interval:</span> <span class="mi">5000</span>
  <span class="p">]</span>
</code></pre></div></div>

<p>즉, 이렇게 세팅하면 콘솔 출력 로거는 사라지고 파일로만 로그가 남는다. 디폴트 핸들러는 그대로 놔두고 파일 로거를 추가해보자.</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">config</span> <span class="ss">:app</span><span class="p">,</span> <span class="ss">:logger</span><span class="p">,</span> <span class="p">[</span>
  <span class="p">{</span><span class="ss">:handler</span><span class="p">,</span> <span class="ss">:file_log</span><span class="p">,</span> <span class="ss">:logger_std_h</span><span class="p">,</span>
   <span class="p">%{</span>
     <span class="ss">config:</span> <span class="p">%{</span>
       <span class="ss">type:</span> <span class="ss">:file</span><span class="p">,</span>
       <span class="ss">file:</span> <span class="sx">~c"logs/app.log"</span><span class="p">,</span>
       <span class="ss">max_no_bytes:</span> <span class="mi">10_000_000</span><span class="p">,</span>
       <span class="ss">max_no_files:</span> <span class="mi">5</span><span class="p">,</span>
       <span class="ss">file_check:</span> <span class="mi">5000</span><span class="p">,</span>
       <span class="ss">filesync_repeat_interval:</span> <span class="mi">5000</span>
     <span class="p">},</span>
     <span class="ss">level:</span> <span class="ss">:info</span><span class="p">,</span>
     <span class="ss">formatter:</span>
     <span class="no">Logger</span><span class="o">.</span><span class="no">Formatter</span><span class="o">.</span><span class="n">new</span><span class="p">(</span>
       <span class="ss">format:</span> <span class="s2">"$date $time [$level] $metadata$message</span><span class="se">\n</span><span class="s2">"</span>
     <span class="p">)</span>
   <span class="p">}}</span>
<span class="p">]</span>
</code></pre></div></div>

<p>config 파일에 파일 로거 핸들러를 추가한다. elixir 구조체를 보기 좋게 하려면 <code class="highlighter-rouge">Logger.Formatter</code> 를 formatter로 세팅한다. 핸들러를 정의했으니 추가할 차례다.</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Logger</span><span class="o">.</span><span class="n">add_handlers</span><span class="p">(</span><span class="ss">:app</span><span class="p">)</span>
</code></pre></div></div>

<p><code class="highlighter-rouge">Application.start/2</code> 콜백에서 정의한 로거 파일 핸들러를 추가한다.</p>

<p>erlang에서 검증된 로거 라이브러리를 사용할 수 있게 됐다. 설정 방법은 부자연스럽다. config 파일 수정 하나로 끝났으면 좋겠는데, 핸들러 정의까지만 가능하다. 핸들러 추가 함수를 따로 호출해 줘야 한다. 핸들러 이름을 명시해서 추가하는 방식이 아니라 config 파일에 <a href="https://hexdocs.pm/elixir/1.17.2/Application.html">Application</a> 이름으로 정의한 핸들러를 통째로 추가하는 방식도 어색하다.</p>

<!----- Footnotes ----->]]></content><author><name>Jongbin Oh</name><email>ohyecloudy@gmail.com</email></author><category term="til" /><category term="elixir" /><category term="logger" /><summary type="html"><![CDATA[elixir 1.15에서 erlang logger 와의 통합이 이뤄졌다. 그래서 elixir에서 erlang의 파일 로거 핸들러를 손쉽게 사용할 수 있다. elixir logger backend가 deprecated 돼서 설정을 바꾸는게 귀찮았지만 erlang의 파일 로거를 사용할 수 있다. elixir 스탠다드 라이브러리에 파일 로거가 직접 만들거나 파일 로거 backend 라이브러리를 추가해야 했다. 이렇게 많이 쓰는 걸 왜 추가하지 않나 했는데, 다 계획이 있었구나.]]></summary></entry><entry><title type="html">#TIL elixir 1.15부터 soft-deprecate된 logger backends 대처 방법</title><link href="https://ohyecloudy.com/ddiary/2025/06/01/til-elixir-erlang-logger-handler/" rel="alternate" type="text/html" title="#TIL elixir 1.15부터 soft-deprecate된 logger backends 대처 방법" /><published>2025-06-01T00:00:00+09:00</published><updated>2025-06-01T00:00:00+09:00</updated><id>https://ohyecloudy.com/ddiary/2025/06/01/til-elixir-erlang-logger-handler</id><content type="html" xml:base="https://ohyecloudy.com/ddiary/2025/06/01/til-elixir-erlang-logger-handler/"><![CDATA[<p>elixir에서 로그를 콘솔로 출력하려고 console logger backend를 정의해서 사용했었다.</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">config</span> <span class="ss">:logger</span><span class="p">,</span> <span class="ss">:console</span><span class="p">,</span>
  <span class="ss">level:</span> <span class="ss">:error</span><span class="p">,</span>
  <span class="ss">format:</span> <span class="s2">"$time $message $metadata"</span>
</code></pre></div></div>

<p>elixir 1.15부터 logger backend는 사용 중단 예정(soft-deprecate)이라 <code class="highlighter-rouge">default_handler</code> 와 <code class="highlighter-rouge">default_formatter</code> 로 설정하는 걸 권장한다.</p>

<blockquote>
  <p>This release also soft-deprecates Elixir’s Logger Backends in favor of Erlang’s Logger handlers. Elixir will automatically convert your :console backend configuration into the new configuration.</p>

  <p><a href="https://elixir-lang.org/blog/2023/06/19/elixir-v1-15-0-released/">Elixir v1.15 released - The Elixir programming language - elixir-lang.org</a></p>
</blockquote>

<p>왜? erlang logger handler를 사용하기 위해서다. <a href="https://elixir-lang.org/blog/2020/10/06/elixir-v1-11-0-released/">elixir 1.11부터 시작한 erlang logger 통합</a>이 마무리되는 느낌이다.</p>

<p>logger backend에 했던 설정을 <code class="highlighter-rouge">default_formatter</code>, <code class="highlighter-rouge">default_handler</code> 에 나눠서 하면 된다.</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">config</span> <span class="ss">:logger</span><span class="p">,</span> <span class="ss">:default_handler</span><span class="p">,</span>
  <span class="ss">level:</span> <span class="ss">:error</span>

<span class="n">config</span> <span class="ss">:logger</span><span class="p">,</span> <span class="ss">:default_formatter</span><span class="p">,</span>
  <span class="ss">format:</span> <span class="s2">"$time $message $metadata"</span>
</code></pre></div></div>

<p>포맷은 <code class="highlighter-rouge">default_formatter</code> 옵션에 하고 포맷 외 설정은 <code class="highlighter-rouge">default_handler</code> 옵션에 한다. erlang의 <a href="https://www.erlang.org/doc/apps/kernel/logger_std_h.html">logger_std_h</a>를 default handler로 사용한다. standard io 출력이 디폴트라서 다른 설정을 안 하는 한 기존의 console backend라고 생각해도 된다.</p>

<blockquote>
  <p>If you want to use the previous :console implementation based on Logger Backends, you can still set backends: [Logger.Backends.Console] and place the configuration under config :logger, Logger.Backends.Console. Although consider using the :logger_backends project in such cases, as Logger.Backends.Console itself will be deprecated in future releases</p>

  <p><a href="https://elixir-lang.org/blog/2023/06/19/elixir-v1-15-0-released/">Elixir v1.15 released - The Elixir programming language - elixir-lang.org</a></p>
</blockquote>

<p>logger backends를 써야만 하는 상황이라면 <a href="https://github.com/elixir-lang/logger_backends">elixir-lang/logger_backends - github.com</a> 라이브러리를 사용하면 된다. 퇴로도 열어준다. 친절하다.</p>

<h1 id="링크">링크</h1>

<ul>
  <li><a href="https://github.com/elixir-lang/logger_backends">elixir-lang/logger_backends - github.com</a>(<a href="http://web.archive.org/web/20250601132710/https://github.com/elixir-lang/logger_backends">archive</a>)</li>
  <li><a href="https://www.erlang.org/doc/apps/kernel/logger_std_h.html">logger_std_h — kernel v10.3 - erlang.org</a>(<a href="http://web.archive.org/web/20250601132716/https://www.erlang.org/doc/apps/kernel/logger_std_h.html">archive</a>)</li>
  <li><a href="https://elixir-lang.org/blog/2023/06/19/elixir-v1-15-0-released/">Elixir v1.15 released - The Elixir programming language - elixir-lang.org</a>(<a href="http://web.archive.org/web/20250601132805/https://elixir-lang.org/blog/2023/06/19/elixir-v1-15-0-released/">archive</a>)</li>
  <li><a href="https://elixir-lang.org/blog/2020/10/06/elixir-v1-11-0-released/">Elixir v1.11 released - The Elixir programming language - elixir-lang.org</a>(<a href="http://web.archive.org/web/20250601132759/https://elixir-lang.org/blog/2020/10/06/elixir-v1-11-0-released/">archive</a>)</li>
</ul>

<!----- Footnotes ----->]]></content><author><name>Jongbin Oh</name><email>ohyecloudy@gmail.com</email></author><category term="til" /><category term="elixir" /><category term="logger" /><category term="deprecated" /><summary type="html"><![CDATA[elixir에서 로그를 콘솔로 출력하려고 console logger backend를 정의해서 사용했었다.]]></summary></entry><entry><title type="html">#TIL #elixir 실패한 테스트 케이스를 손쉽게 확인하는 우회책</title><link href="https://ohyecloudy.com/ddiary/2025/04/05/mix-test-failed/" rel="alternate" type="text/html" title="#TIL #elixir 실패한 테스트 케이스를 손쉽게 확인하는 우회책" /><published>2025-04-05T00:00:00+09:00</published><updated>2025-04-05T00:00:00+09:00</updated><id>https://ohyecloudy.com/ddiary/2025/04/05/mix-test-failed</id><content type="html" xml:base="https://ohyecloudy.com/ddiary/2025/04/05/mix-test-failed/"><![CDATA[<p>Elixir에서 자식 프로젝트가 여러 개 있는 프로젝트를 <a href="https://elixir-lang.org/getting-started/mix-otp/dependencies-and-umbrella-projects.html">Umbrella projects</a>라고 부른다. C++/C#에서 프로젝트가 여러 개 있는 솔루션과 비슷하다. <code class="highlighter-rouge">mix test</code> 태스크를 실행하면 하위 프로젝트에 있는 test가 차례로 실행된다. 테스트가 실패하면 에러 exit code가 세팅되긴 하지만 실패한 테스트 로그를 보기가 힘들다. 스크롤을 한참 올려야 한다.</p>

<p>간단히 우회하기로 했다. 실패한 테스트를 한 번 더 돌리는 것이다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mix <span class="nb">test</span> <span class="o">||</span> <span class="nb">true
</span>mix <span class="nb">test</span> <span class="nt">--failed</span>
</code></pre></div></div>

<p>이렇게 하면 Flaky test(깨지기 쉬운 테스트)가 통과될 수 있는 사이드 이팩트가 있다. 실패한 테스트를 한 번 더 돌려서 통과되는 건 대수롭지 않다. 언젠가는 실패한 테스트를 다시 돌릴 때도 실패하게 될 것이고 그때 확인해서 고치면 된다.</p>

<p>좀 더 나은 방법이 있을 텐데, 지금은 이렇게 깨진 테스트를 편하게 확인하고 있다. <a href="https://github.com/marketplace/actions/test-reporter">Test Reporter</a> GitHub 액션을 사용해도 되지만 아직은 텍스트로 출력하는 게 편하다. 로컬 테스트도 그렇고.</p>

<!----- Footnotes ----->]]></content><author><name>Jongbin Oh</name><email>ohyecloudy@gmail.com</email></author><category term="til" /><category term="elixir" /><category term="testing" /><summary type="html"><![CDATA[Elixir에서 자식 프로젝트가 여러 개 있는 프로젝트를 Umbrella projects라고 부른다. C++/C#에서 프로젝트가 여러 개 있는 솔루션과 비슷하다. mix test 태스크를 실행하면 하위 프로젝트에 있는 test가 차례로 실행된다. 테스트가 실패하면 에러 exit code가 세팅되긴 하지만 실패한 테스트 로그를 보기가 힘들다. 스크롤을 한참 올려야 한다.]]></summary></entry><entry><title type="html">#TIL systemd로 프로세스를 관리하고 systemd-journald로 로그를 본다</title><link href="https://ohyecloudy.com/ddiary/2025/03/22/systemd-journald/" rel="alternate" type="text/html" title="#TIL systemd로 프로세스를 관리하고 systemd-journald로 로그를 본다" /><published>2025-03-22T00:00:00+09:00</published><updated>2025-03-22T00:00:00+09:00</updated><id>https://ohyecloudy.com/ddiary/2025/03/22/systemd-journald</id><content type="html" xml:base="https://ohyecloudy.com/ddiary/2025/03/22/systemd-journald/"><![CDATA[<p><a href="https://ohyecloudy.com/pnotes/archives/deploying-elixir-project-to-heroku/">Heroku</a>를 떠나기로 했다. <a href="https://ohyecloudy.com/pnotes/archives/tbot-800-ex/">Tbot-800.ex</a>은 세심한 관리가 필요한 프로그램이 아니라서 Linux 인스턴스에서 직접 관리해도 충분하다. 두 가지가 필요하다. 프로그램이 크래시로 종료하거나 재부팅을 했을 때, 자동으로 실행해야 한다. 지난 로그도 적당히 볼 수 있으면 좋겠다. <code class="highlighter-rouge">systemd</code> 와 <code class="highlighter-rouge">systemd-journald</code> 로 할 수 있다.</p>

<p><code class="highlighter-rouge">/etc/systemd/system/tbot800.service</code> 파일에 프로그램 실행 정보를 설정한다.</p>

<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Unit]
Description=Tbot-800
After=network.target
StartLimitIntervalSec=0

[Service]
Type=simple
Restart=always
RestartSec=1
User=ubuntu

Environment="ACCOUNT1_INTERVAL_MINUTE=60"
Environment="ACCOUNT2_INTERVAL_MINUTE=60"
# ...

ExecStart=/home/ubuntu/app/bin/tbot800 start

[Install]
WantedBy=multi-user.target
</code></pre></div></div>

<p>이제 <code class="highlighter-rouge">systemctl</code> 프로그램을 실행해서 설정을 로딩하고 재부팅해도 자동으로 시작하게 활성화한다. 그리고 바로 시작한다.</p>

<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo systemctl daemon-reload
$ sudo systemctl enable tbot800.service
$ sudo systemctl start tbot800.service
</code></pre></div></div>

<p>프로그램이 잘 실행되고 있는지 로그를 확인해 보자. <code class="highlighter-rouge">journalctl</code> 프로그램을 실행한다.</p>

<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ journalctl -u tbot800 -f

tbot800[4530]: 13:30:33.648 [info] ########################################
tbot800[4530]: 13:30:33.649 [info] tweet items url: ...
tbot800[4530]: 13:30:33.649 [info] tweet items url: ...
tbot800[4530]: 13:30:33.649 [info] ########################################
tbot800[4530]: 13:30:34.411 [info] shuffle: 535 tweets
tbot800[4530]: 13:30:35.059 [info] shuffle: 626 tweets
</code></pre></div></div>

<p>잘 나온다. 로그를 얼마나 저장할까?</p>

<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat /etc/systemd/journald.conf

[Journal]
Storage=persistent  # 로그를 영구적으로 저장 (기본값: auto)
SystemMaxUse=500M   # 최대 저장 용량 지정 (500MB)
SystemKeepFree=1G   # 최소 남길 여유 공간 (1GB)
MaxRetentionSec=7day # 로그를 7일간 보관
</code></pre></div></div>

<p><code class="highlighter-rouge">/etc/systemd/journald.conf</code> 파일로 설정할 수 있다. Linux 인스턴스 사정에 맞게 용량을 조정한다.</p>

<p><code class="highlighter-rouge">systemd</code> 로 프로세스를 시작하고 <code class="highlighter-rouge">systemd-journald</code> 로 로그를 본다. 좋은 디폴트 옵션이다.</p>

<!----- Footnotes ----->]]></content><author><name>Jongbin Oh</name><email>ohyecloudy@gmail.com</email></author><category term="til" /><category term="systemd" /><category term="journald" /><category term="linux" /><summary type="html"><![CDATA[Heroku를 떠나기로 했다. Tbot-800.ex은 세심한 관리가 필요한 프로그램이 아니라서 Linux 인스턴스에서 직접 관리해도 충분하다. 두 가지가 필요하다. 프로그램이 크래시로 종료하거나 재부팅을 했을 때, 자동으로 실행해야 한다. 지난 로그도 적당히 볼 수 있으면 좋겠다. systemd 와 systemd-journald 로 할 수 있다.]]></summary></entry><entry><title type="html">#TIL GitHub Actions Workflow에서 Postgreql 사용하기</title><link href="https://ohyecloudy.com/ddiary/2025/02/16/github-actions-workflow-postgreql/" rel="alternate" type="text/html" title="#TIL GitHub Actions Workflow에서 Postgreql 사용하기" /><published>2025-02-16T00:00:00+09:00</published><updated>2025-02-16T00:00:00+09:00</updated><id>https://ohyecloudy.com/ddiary/2025/02/16/github-actions-workflow-postgreql</id><content type="html" xml:base="https://ohyecloudy.com/ddiary/2025/02/16/github-actions-workflow-postgreql/"><![CDATA[<p>GitHub Actions Workflow를 사용해 테스트를 돌린다. 테스트할 때, database가 필요해서 사용하는 방법을 알아봤다.</p>

<p>서비스 컨테이너(service container)를 사용하면 도커(docker) 컨테이너를 손쉽게 띄울 수 있다. 아래는 서비스 컨테이너로 postgresql을 실행하는 workflow다. ’<a href="https://docs.github.com/en/actions/use-cases-and-examples/using-containerized-services/creating-postgresql-service-containers">Creating PostgreSQL service containers - GitHub Docs - docs.github.com</a>’ 글을 참고했다.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">jobs</span><span class="pi">:</span>
  <span class="na">test</span><span class="pi">:</span>
    <span class="na">services</span><span class="pi">:</span>
      <span class="na">db</span><span class="pi">:</span>
        <span class="na">image</span><span class="pi">:</span> <span class="s">postgres:12</span>
        <span class="na">ports</span><span class="pi">:</span> <span class="pi">[</span><span class="s1">'</span><span class="s">5432:5432'</span><span class="pi">]</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">POSTGRES_PASSWORD</span><span class="pi">:</span> <span class="s">postgres</span>
          <span class="na">options</span><span class="pi">:</span> <span class="pi">&gt;-</span>
            <span class="s">--health-cmd pg_isready</span>
            <span class="s">--health-interval 10s</span>
            <span class="s">--health-timeout 5s</span>
            <span class="s">--health-retries 5</span>
</code></pre></div></div>

<p>애플리케이션에 database 정보를 전달해야 한다. 전달 방법으로 환경 변수를 선택했다.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">env</span><span class="pi">:</span>
  <span class="na">DATABASE_NAME</span><span class="pi">:</span> <span class="s">mydb</span>
  <span class="na">DATABASE_USERNAME</span><span class="pi">:</span> <span class="s">postgres</span>
  <span class="na">DATABASE_HOSTNAME</span><span class="pi">:</span> <span class="s">localhost</span>
  <span class="na">DATABASE_PASSWORD</span><span class="pi">:</span> <span class="s">postgres</span>
  <span class="na">DATABASE_PORT</span><span class="pi">:</span> <span class="m">5432</span>
</code></pre></div></div>

<p><code class="highlighter-rouge">localhost</code> 가 호스트 이름이다.</p>

<p>database 관련 설정을 모두 환경 변수로 정의했다. 애플리케이션에서는 이 환경 변수를 읽어서 애플리케이션에서 사용할 database 저장소 설정을 하면 된다.</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">config</span> <span class="ss">:awesome_app</span><span class="p">,</span> <span class="no">AwesomeApp</span><span class="o">.</span><span class="no">Repo</span><span class="p">,</span>
  <span class="ss">database:</span> <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">"DATABASE_NAME"</span><span class="p">),</span>
  <span class="ss">username:</span> <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">"DATABASE_USERNAME"</span><span class="p">),</span>
  <span class="ss">password:</span> <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">"DATABASE_PASSWORD"</span><span class="p">),</span>
  <span class="ss">hostname:</span> <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">"DATABASE_HOSTNAME"</span><span class="p">),</span>
  <span class="ss">port:</span> <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">"DATABASE_PORT"</span><span class="p">,</span> <span class="s2">"5432"</span><span class="p">)</span>
</code></pre></div></div>

<p>서비스 컨테이너 최고다. workflow에 필요한 도커 컨테이너를 손쉽게 실행할 수 있다.</p>

<!----- Footnotes ----->]]></content><author><name>Jongbin Oh</name><email>ohyecloudy@gmail.com</email></author><category term="til" /><category term="elixir" /><category term="github" /><category term="posgresql" /><summary type="html"><![CDATA[GitHub Actions Workflow를 사용해 테스트를 돌린다. 테스트할 때, database가 필요해서 사용하는 방법을 알아봤다.]]></summary></entry><entry><title type="html">#TIL Elixir for 포괄 구문(Comprehensions)</title><link href="https://ohyecloudy.com/ddiary/2025/01/23/til-comprehensions-elixir-for-basic/" rel="alternate" type="text/html" title="#TIL Elixir for 포괄 구문(Comprehensions)" /><published>2025-01-23T00:00:00+09:00</published><updated>2025-01-23T00:00:00+09:00</updated><id>https://ohyecloudy.com/ddiary/2025/01/23/til-comprehensions-elixir-for-basic</id><content type="html" xml:base="https://ohyecloudy.com/ddiary/2025/01/23/til-comprehensions-elixir-for-basic/"><![CDATA[<p>Elixir의 for 포괄 구문(<a href="https://hexdocs.pm/elixir/comprehensions.html">Comprehensions</a>)을 잘 사용한 코드 예제를 ’<a href="https://ohyecloudy.com/pnotes/archives/book-testing-elixir-2021/">Testing Elixir (Andrea Leopardi, Jeffrey Matthias, 2021)</a>’ 책에서 봤다.</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">for</span> <span class="n">id</span> <span class="o">&lt;-</span> <span class="mi">100</span><span class="o">..</span><span class="mi">900</span><span class="p">,</span> <span class="n">id</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">all_rain_ids</span> <span class="k">do</span>
    <span class="n">record</span> <span class="o">=</span> <span class="p">%{</span><span class="s2">"dt"</span> <span class="o">=&gt;</span> <span class="n">now_unix</span><span class="p">,</span> <span class="s2">"weather"</span> <span class="o">=&gt;</span> <span class="p">[%{</span><span class="s2">"id"</span> <span class="o">=&gt;</span> <span class="n">id</span><span class="p">}]}</span>

    <span class="n">assert</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="p">[</span><span class="n">weather_struct</span><span class="p">]}</span> <span class="o">=</span> <span class="no">ResponseParser</span><span class="o">.</span><span class="n">parse_response</span><span class="p">(%{</span><span class="s2">"list"</span> <span class="o">=&gt;</span> <span class="p">[</span><span class="n">record</span><span class="p">]})</span>
    <span class="n">assert</span> <span class="n">weather_struct</span><span class="o">.</span><span class="n">rain?</span> <span class="o">==</span> <span class="no">false</span><span class="p">,</span> <span class="s2">"Expected weather id (</span><span class="si">#{</span><span class="n">id</span><span class="si">}</span><span class="s2">) to NOT be a rain condition"</span>
<span class="k">end</span>
</code></pre></div></div>

<p><code class="highlighter-rouge">100..900 |&gt; Enum.filter() |&gt; Enum.each()</code> 구문을 한 번에 표현했다. <code class="highlighter-rouge">:into</code>, <code class="highlighter-rouge">:uniq</code>, <code class="highlighter-rouge">:reduce</code> 옵션도 제공해서 말 그대로 포괄 구문으로 웬만한 건 다 표현할 수 있다. 이 정도는 되어야 달달한 문법(<a href="https://en.wikipedia.org/wiki/Syntactic_sugar">Syntactic sugar</a>, 편의 문법)으로 한 자리를 차지할 수 있다.</p>

<p>쓴다면 과용할 것 같고 그냥 무시하자니 간결하게 표현할 수 있는 자리가 분명히 있을 그런 구문이다.</p>

<!----- Footnotes ----->]]></content><author><name>Jongbin Oh</name><email>ohyecloudy@gmail.com</email></author><category term="til" /><category term="elixir" /><category term="comprehensions" /><category term="syntactic-sugar" /><summary type="html"><![CDATA[Elixir의 for 포괄 구문(Comprehensions)을 잘 사용한 코드 예제를 ’Testing Elixir (Andrea Leopardi, Jeffrey Matthias, 2021)’ 책에서 봤다.]]></summary></entry><entry><title type="html">#TIL asdf .tool-versions 파일로 GitHub Actions에서 erlang, elixir 버전 셋업</title><link href="https://ohyecloudy.com/ddiary/2024/11/30/asdf-github-actions/" rel="alternate" type="text/html" title="#TIL asdf .tool-versions 파일로 GitHub Actions에서 erlang, elixir 버전 셋업" /><published>2024-11-30T00:00:00+09:00</published><updated>2024-11-30T00:00:00+09:00</updated><id>https://ohyecloudy.com/ddiary/2024/11/30/asdf-github-actions</id><content type="html" xml:base="https://ohyecloudy.com/ddiary/2024/11/30/asdf-github-actions/"><![CDATA[<p>프로젝트별 erlang과 elixir 버전 관리를 <a href="https://asdf-vm.com/">Asdf</a>로 하고 있다. <code class="highlighter-rouge">.tool-versions</code> 파일로 사용하는 버전을 정의한다. 지금 사용 중인 <code class="highlighter-rouge">.tool-versions</code> 내용은 아래와 같다.</p>

<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>erlang 26.2.5.3
elixir 1.16.3-otp-26
</code></pre></div></div>

<p>변경 사항이 생기면 자동으로 테스트를 돌리는 데 사용하는 <a href="https://docs.github.com/ko/actions">GitHub Action</a>에서 asdf를 사용하지 못한다. 그래서 아래와 같이 버전을 똑같이 수동으로 맞춰서 사용하고 있었다.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">strategy</span><span class="pi">:</span>
  <span class="c1"># Specify the OTP and Elixir versions to use when building</span>
  <span class="c1"># and running the workflow steps.</span>
  <span class="na">matrix</span><span class="pi">:</span>
    <span class="na">otp</span><span class="pi">:</span> <span class="pi">[</span><span class="s1">'</span><span class="s">26.2.5.3'</span><span class="pi">]</span>       <span class="c1"># Define the OTP version [required]</span>
    <span class="na">elixir</span><span class="pi">:</span> <span class="pi">[</span><span class="s1">'</span><span class="s">1.16.3'</span><span class="pi">]</span>    <span class="c1"># Define the elixir version [required]</span>
<span class="na">steps</span><span class="pi">:</span>
  <span class="c1"># Step: Setup Elixir + Erlang image as the base.</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Set up Elixir</span>
    <span class="na">uses</span><span class="pi">:</span> <span class="s">erlef/setup-beam@v1</span>
    <span class="na">with</span><span class="pi">:</span>
      <span class="na">otp-version</span><span class="pi">:</span> <span class="s">$</span>
      <span class="na">elixir-version</span><span class="pi">:</span> <span class="s">$</span>
</code></pre></div></div>

<p>asdf는 사용하지 못하지만 <code class="highlighter-rouge">.tool-versions</code> 파일은 사용할 수 있다는 걸 최근에 알았다.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Step: Setup Elixir + Erlang image as the base.</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Set up Elixir</span>
  <span class="na">uses</span><span class="pi">:</span> <span class="s">erlef/setup-beam@v1</span>
  <span class="na">with</span><span class="pi">:</span>
    <span class="na">version-file</span><span class="pi">:</span> <span class="s">.tool-versions</span>
    <span class="na">version-type</span><span class="pi">:</span> <span class="s">strict</span>
</code></pre></div></div>

<p>이제 erlang, elixir 버전을 <code class="highlighter-rouge">.tool-versions</code> 한 곳에서만 관리하면 된다.</p>

<p>관련 커밋 <a href="https://github.com/ohyecloudy/template-elixir/commit/da81b1f93f4193759f9d00b44e3d66614eea9ff0">da81b1f93f4</a></p>

<!----- Footnotes ----->]]></content><author><name>Jongbin Oh</name><email>ohyecloudy@gmail.com</email></author><category term="til" /><category term="asdf" /><category term="github-actions" /><category term="elixir" /><summary type="html"><![CDATA[프로젝트별 erlang과 elixir 버전 관리를 Asdf로 하고 있다. .tool-versions 파일로 사용하는 버전을 정의한다. 지금 사용 중인 .tool-versions 내용은 아래와 같다.]]></summary></entry></feed>