티스토리 뷰
Ruby on Rails에서는 기본적으로 서버를 키는 순간부터 로그기록이 시작됩니다.
로그는 서버의 성공부터 작업 실패까지 모든활동기록 을 담고 있고 해당 활동 때 무엇을 했는지에 대한 기록이 다 나타나있습니다. 그리고 만약에 언제 서버에 문제가 터졌을 경우에, 해당 로그기록을 보면 어떤 오류를 발생하며 문제가 생겼는지 대응을 할 수 있습니다.
사람은 24시간 내내 서버의 이벤트를 살펴볼 수 있지 않습니다. 그렇다보니 사실 로그파일은 성공에 대한 작업보단 실패에 대한 작업(라우터 경로 에러, 메소드 오류, nil 오류, 등) 등을 살펴보는 목적이 더 큽니다.
서비스를 운영함에 있어서 어느 날 특정 사용자 그륩만이 서비스 이용이 안된다고 제보가 왔거나, 예기치 못한 상황으로 서버가 죽었을 경우, 제일 먼저 살펴보는게 코드가 아닌 아마 서버 내에 있는 로그파일이 아닐까 싶습니다. (일단 코드 내에 뭐가 잘못 쓰여졌는지 보기보단, 무슨 사유로 안되는지 기록을 살펴봐야 하니까요.)
저희는 카톡 대화내용과 같이 언급된 로그기록을 통해 앞으로 이어질 내용을 통해 의문점을 해결해보고자 합니다 :
1. 로그파일 속에서 특정 기록에 대해 검색할 때에는 ubuntu에서 어떤 명령어로 탐색을 해야 할 것인가?
2. 3월 31일 22시 50분 기록을 요청했으나, 왜 위 사진에서는 3월 31일 13시 50분(로그기록 상에는 50분에 기록이 없어서 49분으로 검색했습니다.) 으로 검색을 시도했는가?
3. INFO, DEBUG 이런건 도대체 무엇인가?
일단 로그에 대해 이야기를 하기전에 앞서, 간단한 Log의 특징을 살펴보겠습니다.
Ruby on Rails에서 기본으로 설정된 log파일의 특징은 다음과 같습니다 :
1. Ruby on Rails 기준으로 로그파일은 기본적으로 log 폴더속에 생성이 됩니다.
2. log 파일의 이름은 기본적으로 Environment 이름을 따릅니다.
* 예 : Production 환경에서 서버를 Open 시, log/production.log 파일이 생성
3. log파일 속에는 이벤트가 발생된 시간, Request ID, 규칙(INFO, DEBUG 등)이 자동으로 입력이 되며 생성이 됩니다.
-
로그 기록을 탐색하는 방법
로그 기록을 탐색하는 방법은 개인적으로는 4가지의 방법을 쓰고 있습니다.
1) 최초 로그 시작점 부터 n번째 까지의 로그기록을 살펴보고 싶을 때
## 최초 시작점 부터 30번째 줄 까지의 로그기록 조회
head -30 production.log
2) 마지막 로그 종료 시점부터 n번 째 까지의 로그기록을 살펴보고 싶을 때
* 필자는 개인적으로 해당 기능을 많이 씁니다.
## 종료지점으로 부터 17번째 줄 까지의 로그기록 조회
tail -17 production.log
3) 특정 내용을 가진 로그기록을 찾아낼 때
cat production.log | grep "FATAL"
4) 특정 내용을 기준으로 위/아래 n줄의 텍스트 기록이 보여지게 하기
## '2020-04-07T04:10:33.576758' 글자를 기준으로 위/아래 3줄의 내용도 함께 보기
grep -3 "2020-04-07T04:10:33.576758" "./production.log"
-
로그 규칙
로그에는 크게 trace, debug, info, warn, error, fatal 6단계의 규칙이 있으며, 로그에 따른 레벨은 위 사진과 같습니다.
위 규칙을 Log4j 라고 하는데, 이 규칙들은 자바 기반의 오픈소스에서 유래된 로그규칙입니다.
참고 Ruby on Rails에서는 trace 규칙은 지원하지 않습니다.
1) Trace : 광범위한 로그의 규칙을 통합하잔 의미로 나온 규칙
* 해당 규칙은 log4j1.2.12 버전에서 신규추가 됐다고 합니다.
2) Debug : 개발 혹은 프로그램 실행 시, 디버깅의 성격을 띄는 규칙
D, [2020-03-30T07:25:53.355568 #23633] DEBUG -- : [de91acc7-8893-480e-bf2f-47d2867e56e5] Model Load (2.3ms) SELECT "tables".* FROM "tables" ORDER BY date DESC LIMIT $1 [["LIMIT", 5]]
3) Info : 사용자의 이벤트를 담는 규칙 (예 : 메인 페이지로 이동을 한다, 로그인을 했다.)
I, [2020-03-30T07:25:53.358574 #23633] INFO -- : [de91acc7-8893-480e-bf2f-47d2867e56e5] Completed 200 OK in 7ms (Views: 4.2ms | ActiveRecord: 2.3ms)
I, [2020-03-30T07:27:35.643499 #23633] INFO -- : [cda113ce-4a85-4d50-9ceb-994796b3226d] Started GET "/api/v53/..." for 39.7.47.164 at 2020-03-30 07:27:35 +0000
4) Warn : 지금은 처리를 할 수 있으나, 향후 문제가 될 수 있는 행위에 대해 경고를 띄는 규칙
W, [2020-01-25T16:20:52.484506 #29610] WARN -- : [560423f9-8f06-43c3-8252-3c5776c564ad] Scoped order is ignored, it's forced to be batch order.
5) Error : 요청 처리 중, 문제 발생 (예 : Background Job 도중 Cannot allocate memory)
[2020-01-15T19:08:10.898110 #8174] ERROR -- : [ActiveJob] [BackgroundJob] [a49d7f1e-bf40-4899-8812-9af0f4f8f556] Error performing BackgroundJob (Job ID: a49d7f1e-bf40-4899-8812-9af0f4f8f556) from Sidekiq(×××××_crawl) in 75.77ms: Errno::ENOMEM (Cannot allocate memory - which):
6) Fatal : 심각한 오류를 표현하는 규칙 (예 : NO Router Match)
F, [2020-03-11T02:41:44.758998 #14355] FATAL -- : [7ce864bd-16db-4d25-82c8-14f2b931f68c] ActionController::RoutingError (No route matches [GET] "/qwert"):
-
로그가 기록되는 시간 설정
처음에 해당 글 서론에서 제가 던진 의문점 중 하나인, '3월 31일 22시 50분 기록을 왜 3월 31일 13시 50분 시간으로 검색을 해봤는가?' 에 대한 의문을 풀어보겠습니다.
## 2020년 4월 9일 03시 20분 52초 때 작성된 로그
W, [2020-04-08T18:20:52.484506 #29610] WARN -- : [560423f9-8f06-43c3-8252-3c5776c564ad] Scoped order is ignored, it's forced to be batch order.
위 로그기록에는 2020년 4월 8일 18시 20분 52초에 기록된 로그라고는 하지만, 실제로는 2020년 4월 9일 03시 20분 52초에 기록된 로그입니다.
이렇게 시간이 안맞는 이유는 서버 시간과 연관이 있습니다.
기본적으로 ubuntu 등 서버 시간은 한국 표준시(UTC+9)로 설정되어있지 않고, 영국 표준시(UTC+0)로 설정되어 있습니다. 로그기록에 기록되는 시간은 서버시간 기준을 따라서 작성이 되다보니, 이러한 문제가 발생하는겁니다.
참고로 config/application 파일에서 locale Time을 설정을 해도, 로그파일에 기록되는 시간에는 영향을 끼치지 않습니다.
한국 표준시에 대한 서버 시간을 설정하는 방법은 간단하게, 터미널에 다음 명령어를 입력하면 됩니다.
## [Ubuntu] 서버의 Timezone을 한국(UTC+9)으로 바꾸는 명령어
sudo ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
부록 [Ubuntu] 우분투(Ubuntu)에서 타임존 변경하기
참고 현재 운영되고 있는 캐치딜 서버도 시간의 오차에 대한 부분을 인지 후, 4월 8일부터 로그파일에서 UTC+9 기준으로 시간 기록이 남겨지고 있습니다.
-
로그파일 관리 유의사항
부록 Ruby on Rails : 스케쥴링(예약된 시간) Background Job [Gem : Whenever]
참고 과정을 진행하면서 whenever 관련 코드에 대한 자세한 설명은 생략합니다.
로그파일은 서버가 운용되는동안 끊임없이 내용이 작성이 되다보니, 용량에 있어 은근히 많은공간을 차지하게 됩니다. 그렇다보니 가끔 오래된 로그기록에 대해선 삭제를 해야하는 관리가 필요합니다.
저 같은 경우에는 1달에 한번 씩 scheduler Job을 이용해서 production.log 와 같은 서버 로그파일을 [YEAR]-[MONTH]-production.log와 같은 형식으로 백업(copy) 후, production.log의 내용을 비우는 형식으로 관리를 하고 있습니다.
그리고 3개월에 한번 씩 기존에 백업해둔 로그파일을 지우는 작업이 진행이 됩니다.
해당 과정을 직접 한번 설계해보겠습니다.
1. scheduler Job을 돕는 whenever Gem을 설치해보겠습니다.
Gemfile 을 열람 후, 아래 코드 내용을 추가해주세요.
gem 'whenever'
그리고 Gem을 설치해주세요.
bundle install
2. task 파일을 생성합니다.
rails g task backup_log job
3. lib/tasks/backup_log.rake 파일로 이동 후, 아래의 코드를 입력해주세요.
## lib/tasks/backup_log.rake
namespace :backup_log do
desc "TODO"
task job: :environment do
def backup_log
copyServerLog = "cd #{Rails.root}/log; cp #{Rails.env}.log #{Time.now.in_time_zone("Asia/Seoul").strftime('%Y')}-#{Time.now.in_time_zone("Asia/Seoul").strftime('%m').to_i}-#{Rails.env}.log; > #{Rails.env}.log"
system copyServerLog
list = ""
currentMonth = Time.now.in_time_zone("Asia/Seoul").strftime('%m').to_i
currentYear = Time.now.in_time_zone("Asia/Seoul").strftime('%Y').to_i
if currentMonth % 3 == 0 && currentMonth == 3
## job이 실행되는 날이 3월일 경우, 작년 12월, 1~2월에 기록된 코드 삭제
list << "#{currentYear-1}-12-#{Rails.env}.log "
for i in 1..2
list << "#{currentYear}-#{i}-#{Rails.env}.log "
end
elsif currentMonth % 3 == 0 && currentMonth != 3
## job이 실행되는 날이 6, 9, 12월일 경우, 해당 월-3 ~ 해당 월-1월에 기록된 코드 삭제
for i in currentMonth-3..currentMonth-1
list << "#{currentYear}-#{i}-#{Rails.env}.log "
end
end
deleteLog = "cd #{Rails.root}/log; rm -rf #{list}"
system deleteLog
end
backup_log
end
end
참고 위 코드는 1개월 단위로 돌리는 방향으로 개발했습니다.
참고 1개월 마다 로그파일이 백업이 되고, 3개월마다 이전의 백업했던 로그기록이 삭제됩니다.
4. 프로젝트 루트 위치(app, config, db, lib, bin, Gemfile 등등 디렉토리 및 파일이 있는 위치)에서 Scheduler Job 설정하는 파일을 생성합니다.
wheneverize .
5. config/scheduler.rb 파일에서 다음과 같이 scheduler Job 규칙을 입력합니다.
## config/schedule.rb
every '* 14 28 * *' do
rake "backup_log:job", :environment => "[환경]"
end
참고1 Scheduler 규칙은 크론탭을 활용했습니다.
참고2 위 코드는 매월 28일 오후 23시에 작동이 됩니다.
참고3 위 코드에 설정된 시간은 한국 표준시(UTC+9)가 아닌 영국 표준시(UTC+0) 입니다.
6. 최종적으로 아래 코드를 입력하면 scheduler 작동이 시작됩니다.
whenever --update-crontab
# => [write] crontab file updated
참고 scheduler Job을 멈추고 싶을 경우
whenever -c
-
실습 : 직접 로그파일 만들어보기
부록 Ruby on Rails : 선입선출 Background Job [Gem : sidekiq]
참고 과정을 진행하면서 sidekiq 관련 코드에 대한 자세한 설명은 생략합니다.
간혹 개발을 하면서 별개로 로그파일을 만들어야 하는 순간이 올 수 있습니다. (예를들어 sidekiq Job에 대한 로그기록 파일 생성 및 로직 구현)
이런 상황을 대비해서 sidekiq을 위한 로그파일을 만들어보겠습니다.
1. redis를 설치합니다.
redis 설치과정은 해당 글에서 Chapter 1 부분을 참고해주세요.
2. sidekiq Gem을 설치합니다.
## Gemfile
# sidekiq
gem 'sidekiq'
gem 'connection_pool'
gem 'redis-namespace'
2. config/application.rb 로 이동 후, Background Job에 쓰일 수단에 대해 정의합니다.
config.active_job.queue_adapter = :sidekiq
## config/application.rb
... (내용 생략) ...
module Kcm
class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
+ config.active_job.queue_adapter = :sidekiq
config.load_defaults 5.2
# config.active_job.queue_name_prefix = Rails.env
# Settings in config/environments/* take precedence over those specified here.
# Application configuration can go into files in config/initializers
# -- all .rb files in that directory are automatically loaded after loading
# the framework and any gems in your application.
end
end
3. config/application.rb 상단에 모듈(require) 을 정의합니다.
require 'sidekiq/web'
## config/application.rb
... (내용 생략) ...
+ require 'sidekiq/web'
module Kcm
class Application < Rails::Application
+ config.active_job.queue_adapter = :sidekiq
... (내용 생략) ...
end
end
4. config 위치에서 sidekiq.yml 파일을 새로 생성 후, 다음 내용을 작성해서 각각 기능에 대해 초기 설정을 합니다.
## config/sidekiq.yml
# 사실 logfile 이 의미가 있긴 할지 모르겠지만.. 일단 적어두겠습니다.
# (실제로 logfile 위치를 가리키는것은 config/initializers/sidekiq.rb 에 입력했던 log_file_path 변수 입니다.)
:pidfile: ./tmp/pids/sidekiq.pid
:daemon: true
development:
:verbose: true
:logfile: ./log/development_sidekiq.log
:concurrency: 3
:timeout: 30
:queues:
- simple_sidekiq_test
production:
:verbose: false
:logfile: ./log/production_sidekiq.log
:concurrency: 3
:timeout: 5
:queues:
- simple_sidekiq_test
5. config/initializers 위치로 이동 후, sidekiq.rb 및 redis.rb 파일을 새로 생성 후 코드를 입력해주세요.
1) config/initializers/sidekiq.rb
## config/initializers/sidekiq.rb
# LOG파일 위치 지정
$log_file_path = "#{Rails.root}/log/#{Rails.env}_sidekiq.log"
# Development Environment => $log_file_path = "#{Rails.root}/log/development_sidekiq.log"
# Production Environment => $log_file_path = "#{Rails.root}/log/production_sidekiq.log"
Sidekiq.configure_server do |config|
config.redis = REDIS_POOL
## sidekiq 설정 내에서 로그파일 생성 정의
config.logger = Logger.new(log_file_path)
end
Sidekiq.configure_client do |config|
config.redis = REDIS_POOL
end
# [Option] Exception 사유로 Job이 종료가 될 시, 다시 Job 시도 및 횟수를 정하는 코드입니다.
Sidekiq.default_worker_options = {retry: 1}
참고1-1 서버가 켜지면 $log_file_path 라는 이름의 전역변수 생성과 동시에, 로그파일이 저장될 path에 대해 string 형식으로 표현됩니다.
2) config/initializers/redis.rb
## config/initializers/redis.rb
# Sidekiq threadpool 생성
REDIS_POOL = ConnectionPool.new(:size => 7 + 5, :timeout => 2) { Redis::Namespace.new("sidekiq_job", redis: Redis.new(:url => "redis://localhost:6379")) }
6. config 폴더에 application.yml 파일을 생성 후, 5번 과정 때 작성했던 환경변수에 대한 Value를 입력합니다.
7. config/routes.rb 에서 다음 내용을 추가해주세요.
mount Sidekiq::Web => '/sidekiq'
적용 예시 :
## routes.rb
Rails.application.routes.draw do
... (내용 생략) ...
+ mount Sidekiq::Web => '/sidekiq'
end
8. 터미널에 다음 명령어를 입력해서 redis-server을 켜주세요. (이미 켜져있을 경우 껏다 켜주세요.)
참고 screen[클릭] 을 활용해서 redis server을 킬 것을 추천드립니다.
redis-server /etc/redis/redis.conf
9. 터미널에 다음 명령어를 입력해서 sidekiq 프로세스 및 콘솔을 킵니다.
참고 screen[클릭] 을 활용해서 sidekiq을 킬 것을 추천드립니다.
sidekiq
참고 1 sidekiq 종료는 키보드에서 Ctrl+C 키를 눌러주면 됩니다.
참고 2 Production 에서 사용 시에는 아래 명령어를 입력해줘야 합니다. screen[클릭] 을 활용해서 사용할 것을 추천드립니다.
## Production Environment
RAILS_ENV=production rails c
10. 간단히 BackGround Job을 돌려볼 Active Job 파일을 생성해보겠습니다.
rails g job simple_nokogiri
11. app/jobs/simple_nokogiri_job.rb 파일로 이동 후, 간단히 크롤링을 돌리는 코드와 그 사이에 로그기록을 하는 코드를 추가해보겠습니다.
## app/jobs/simple_nokogiri_job.rb
class SimpleNokogiriJob < ApplicationJob
queue_as :simple_sidekiq_test
require 'open-uri'
def perform(*args)
begin
## 로그 기록을 하기위한 파일 설정
sidekiq_logger = Logger.new(STDOUT)
sidekiq_logger = Logger.new($log_file_path)
doc = Nokogiri::HTML(open("https://media.daum.net/ranking/popular/")) # 열고
crawl_data = doc.css('div.rank_news > ul.list_news2 > li')
sidekiq_logger.info "크롤링 시작"
sidekiq_logger.info " ↳ START at sidekiq JOB ID : #{@job_id}"
crawl_data.each do |t|
title = t.css('div.cont_thumb > strong.tit_thumb > a').text
sidekiq_logger.debug "[#{@job_id.truncate(20, omission: "...")}] #{title}"
end
sidekiq_logger.info " ↱ Completely END at sidekiq JOB ID : #{@job_id}"
sidekiq_logger.info "크롤링 종료"
## 파일 종료
sidekiq_logger.close
rescue
sidekiq_logger.info " ↱ Force Stop at sidekiq JOB ID : #{@job_id}"
sidekiq_logger.fatal "예기치 않은 오류로 인한 강제 종료"
sidekiq_logger.debug $!
## 파일 종료
sidekiq_logger.close
end
end
end
참고1 위 코드는 Nokogiri Gem을 이용해서 카카오뉴스에서 인기뉴스 제목을 크롤링 하는 코드입니다.
참고2 sidekiq_logger 변수는 로그파일에 대한 정보를 가지고있는 변수입니다.
참고2 $! 은 오류 발생 시 오류에 대한 정보(디버그)를 가지고있습니다.
12. 터미널에서 Rails Console을 킨 후, Console에 다음 명령어를 입력해보세요.
SimpleNokogiriJob.perform_later
위 명령어를 입력하면 이제 simple_nokogiri_job.rb 파일 속 코드가 Background Job으로서 sidekiq 내에서 실행이 됩니다.
13. log/development_sidekiq.rb 파일을 열람해서 로그기록이 잘 작성이 되었는지, 어떻게 작성이 되었는지 확인해보세요!
# Logfile created on 2020-04-09 04:48:59 +0000 by logger.rb/66358
I, [2020-04-09T04:48:59.504199 #10524] INFO -- : Booted Rails 5.2.4.1 application in development environment
I, [2020-04-09T04:48:59.504288 #10524] INFO -- : Running in ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]
I, [2020-04-09T04:48:59.504313 #10524] INFO -- : See LICENSE and the LGPL-3.0 for licensing details.
I, [2020-04-09T04:48:59.504330 #10524] INFO -- : Upgrade to Sidekiq Pro for more features and support: http://sidekiq.org
D, [2020-04-09T04:48:59.506189 #10524] DEBUG -- : Client Middleware:
D, [2020-04-09T04:48:59.506227 #10524] DEBUG -- : Server Middleware:
I, [2020-04-09T04:48:59.506249 #10524] INFO -- : Starting processing, hit Ctrl-C to stop
D, [2020-04-09T04:48:59.506331 #10524] DEBUG -- : {:queues=>["simple_sidekiq_test"], :labels=>[], :concurrency=>3, :require=>".", :environment=>nil, :timeout=>30, :poll_interval_average=>nil, :average_scheduled_poll_interval=>5, :error_handlers=>[#<Sidekiq::ExceptionHandler::Logger:0x00000000026f2c08>], :death_handlers=>[], :lifecycle_events=>{:startup=>[], :quiet=>[], :shutdown=>[], :heartbeat=>[]}, :dead_max_jobs=>10000, :dead_timeout_in_seconds=>15552000, :reloader=>#<Sidekiq::Rails::Reloader @app=Kcm::Application>, :pidfile=>"./tmp/pids/sidekiq.pid", :daemon=>true, :production=>{:verbose=>false, :logfile=>"./log/production_sidekiq.log", :concurrency=>3, :timeout=>5, :queues=>["simple_sidekiq_test"]}, :verbose=>true, :logfile=>"./log/development_sidekiq.log", :config_file=>"./config/sidekiq.yml", :strict=>true, :tag=>"environment", :identity=>"ip-172-31-31-71:10524:db20c84b355e"}
I, [2020-04-09T04:49:03.183703 #10524] INFO -- : start
I, [2020-04-09T04:49:03.296754 #10524] INFO -- : 크롤링 시작
I, [2020-04-09T04:49:03.296972 #10524] INFO -- : ↳ START at sidekiq JOB ID : c141706d-94d6-4999-b142-691ea87f95f9
D, [2020-04-09T04:49:03.297208 #10524] DEBUG -- : [c141706d-94d6-499...] [르포] '아베 퇴진시위'에 '도쿄탈출'까지..日 '긴급사태 회의론' 확산
D, [2020-04-09T04:49:03.298193 #10524] DEBUG -- : [c141706d-94d6-499...] "밥값 900억 먼저 낼게요"..손님 끊긴 식당가 반가운 '큰손'
D, [2020-04-09T04:49:03.298341 #10524] DEBUG -- : [c141706d-94d6-499...] 코로나 퍼지자 꼭꼭 숨은 지도자..서민은 아사 걱정까지
D, [2020-04-09T04:49:03.309265 #10524] DEBUG -- : [c141706d-94d6-499...] [여기는 인도] 자가격리자 집에 '방문금지' 딱지, 손등엔 도장..인권침해 논란
D, [2020-04-09T04:49:03.309381 #10524] DEBUG -- : [c141706d-94d6-499...] 에스토니아 "진단키트 계속 공급 원해"..文대통령 "적극 공유"
I, [2020-04-09T04:49:03.309405 #10524] INFO -- : ↱ Completely END at sidekiq JOB ID : c141706d-94d6-4999-b142-691ea87f95f9
I, [2020-04-09T04:49:03.309419 #10524] INFO -- : 크롤링 종료
I, [2020-04-09T04:49:03.310310 #10524] INFO -- : done
-
정리
위 과정을 통해 로그의 기본개념, 로그 규칙, 로그를 살펴보는법, 로그 관리 유의사항, 간단히 로그 만들어보기를 해봤습니다.
사실 로그파일이 뭔가 지금 당장으로선 중요성이 떨어지고, 자주 쓰이는게 아니다보니 처음에 로그파일의 중요성을 깨우치기가 쉽지 않은 것 같습니다. 저 또한 로그파일의 중요성을 나중에서야 깨달았으니까요..
-
읽으면 좋은 자료
1. Rails Logger and Rails Logging Best Practices
-
자료 참고
1. [Ubuntu] 우분투(Ubuntu)에서 타임존 변경하기
2. how to find a word in text files from a directory [duplicate]
'프로그래밍 공부 > Ruby on Rails : 이론' 카테고리의 다른 글
Ruby on Rails : Credentials (0) | 2020.04.17 |
---|---|
Ruby on Rails : Table JOIN (0) | 2020.01.31 |
Ruby on Rails : 다른 AWS EC2 서버 내의 Database와 Remote Connect 하기 (0) | 2020.01.19 |
검색 기능 구현의 고민 : 공백처리 (1) | 2019.12.14 |
SQLite, PostgreSQL 차이 : Like / iLike (2) | 2019.12.13 |