액티브 서포트 계측
액티브 서포트는 Ruby 언어 확장, 유틸리티 및 기타 기능을 제공하는 Rails의 핵심 부분입니다. 포함된 기능 중 하나는 Ruby 코드 내부의 특정 작업을 측정하기 위한 계측 API입니다. 이는 Rails 애플리케이션 또는 프레임워크 자체 내부에서 사용할 수 있습니다. 그러나 Rails에 국한되지 않으며 다른 Ruby 스크립트에서도 독립적으로 사용할 수 있습니다.
이 가이드에서는 액티브 서포트의 계측 API를 사용하여 Rails 및 기타 Ruby 코드 내부의 이벤트를 측정하는 방법을 배울 것입니다.
이 가이드를 읽고 나면 다음을 알 수 있습니다:
- 계측이 제공할 수 있는 것.
- 훅에 구독자를 추가하는 방법.
- Rails 프레임워크 내의 계측 훅.
- 사용자 정의 계측 구현을 구축하는 방법.
계측 소개
액티브 서포트가 제공하는 계측 API를 통해 개발자는 다른 개발자가 훅에 연결할 수 있는 훅을 제공할 수 있습니다. Rails 프레임워크 내에는 여러 개의 이러한 훅이 있습니다. 이 API를 사용하면 개발자는 애플리케이션 또는 다른 Ruby 코드 내부에서 특정 이벤트가 발생할 때 알림을 받도록 선택할 수 있습니다.
예를 들어, Active Record 내에는 Active Record가 데이터베이스에서 SQL 쿼리를 사용할 때마다 호출되는 훅이 있습니다. 이 훅에 구독하여 특정 작업 중 쿼리 수를 추적할 수 있습니다. 또한 컨트롤러 작업 처리에 대한 훅이 있습니다. 이를 사용하여 특정 작업의 실행 시간을 추적할 수 있습니다.
또한 애플리케이션 내에서 사용자 정의 이벤트를 만들고 나중에 구독할 수 있습니다.
이벤트 구독하기
단일 인수 블록을 사용하여 ActiveSupport::Notifications.subscribe
를 사용하면 모든 알림을 수신할 수 있습니다. 블록이 받는 인수 수에 따라 다른 데이터를 받게 됩니다.
이벤트를 구독하는 첫 번째 방법은 단일 인수 블록을 사용하는 것입니다. 인수는 ActiveSupport::Notifications::Event
인스턴스가 됩니다.
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |event|
event.name # => "process_action.action_controller"
event.duration # => 10 (in milliseconds)
event.allocations # => 1826
event.payload # => {:extra=>information}
Rails.logger.info "#{event} Received!"
end
이벤트 객체의 모든 데이터가 필요하지 않은 경우 다음과 같이 5개의 인수를 받는 블록을 지정할 수 있습니다:
- 이벤트 이름
- 시작 시간
- 종료 시간
- 이벤트를 발생시킨 계측기의 고유 ID
- 이벤트 페이로드
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |name, started, finished, unique_id, payload|
# 사용자 정의 코드
Rails.logger.info "#{name} Received! (started: #{started}, finished: #{finished})" # process_action.action_controller Received! (started: 2019-05-05 13:43:57 -0800, finished: 2019-05-05 13:43:58 -0800)
end
정확한 경과 시간을 계산하려면 ActiveSupport::Notifications.monotonic_subscribe
를 사용하세요. 제공된 블록은 위와 동일한 인수를 받지만 started
와 finished
에는 정확한 단조 시간 값이 포함됩니다.
ActiveSupport::Notifications.monotonic_subscribe "process_action.action_controller" do |name, started, finished, unique_id, payload|
# 사용자 정의 코드
duration = finished - started # 1560979.429234 - 1560978.425334
Rails.logger.info "#{name} Received! (duration: #{duration})" # process_action.action_controller Received! (duration: 1.0039)
end
정규 표현식과 일치하는 이벤트에 구독할 수도 있습니다. 이를 통해 한 번에 여러 이벤트에 구독할 수 있습니다. 다음은 ActionController
의 모든 이벤트에 구독하는 방법입니다:
ActiveSupport::Notifications.subscribe(/action_controller/) do |event|
# ActionController 이벤트 모두 검사
end
Rails 프레임워크 훅
Ruby on Rails 프레임워크 내에는 일반적인 이벤트에 대한 여러 훅이 제공됩니다. 이러한 이벤트와 해당 페이로드는 아래에 자세히 설명되어 있습니다.
Action Controller
start_processing.action_controller
키 |
값 |
:controller |
컨트롤러 이름 |
:action |
작업 |
:request |
ActionDispatch::Request 객체 |
:params |
필터링된 매개변수를 제외한 요청 매개변수 해시 |
:headers |
요청 헤더 |
:format |
html/js/json/xml 등 |
:method |
HTTP 요청 동사 |
:path |
요청 경로 |
{
controller: "PostsController",
action: "new",
params: { "action" => "new", "controller" => "posts" },
headers: #<ActionDispatch::Http::Headers:0x0055a67a519b88>,
format: :html,
method: "GET",
path: "/posts/new"
}
process_action.action_controller
키 |
값 |
:controller |
컨트롤러 이름 |
:action |
작업 |
:params |
필터링된 매개변수를 제외한 요청 매개변수 해시 |
:headers |
요청 헤더 |
:format |
html/js/json/xml 등 |
:method |
HTTP 요청 동사 |
:path |
요청 경로 |
:request |
ActionDispatch::Request 객체 |
:response |
ActionDispatch::Response 객체 |
:status |
HTTP 상태 코드 |
:view_runtime |
뷰에서 소요된 시간(ms) |
:db_runtime |
데이터베이스 쿼리 실행에 소요된 시간(ms) |
{
controller: "PostsController",
action: "index",
params: {"action" => "index", "controller" => "posts"},
headers: #<ActionDispatch::Http::Headers:0x0055a67a519b88>,
format: :html,
method: "GET",
path: "/posts",
request: #<ActionDispatch::Request:0x00007ff1cb9bd7b8>,
response: #<ActionDispatch::Response:0x00007f8521841ec8>,
status: 200,
view_runtime: 46.848,
db_runtime: 0.157
}
send_file.action_controller
추가 키는 호출자에 의해 추가될 수 있습니다.
send_data.action_controller
ActionController
는 페이로드에 특정 정보를 추가하지 않습니다. 모든 옵션이 페이로드로 전달됩니다.
redirect_to.action_controller
{
status: 302,
location: "http://localhost:3000/posts/new",
request: <ActionDispatch::Request:0x00007ff1cb9bd7b8>
}
halted_callback.action_controller
{
filter: ":halting_filter"
}
unpermitted_parameters.action_controller
키 |
값 |
:keys |
허용되지 않은 키 |
:context |
다음 키를 포함하는 해시: :controller , :action , :params , :request |
send_stream.action_controller
키 |
값 |
:filename |
파일 이름 |
:type |
HTTP 콘텐츠 유형 |
:disposition |
HTTP 콘텐츠 디스포지션 |
{
filename: "subscribers.csv",
type: "text/csv",
disposition: "attachment"
}
Action Controller: 캐싱
write_fragment.action_controller
{
key: 'posts/1-dashboard-view'
}
read_fragment.action_controller
{
key: 'posts/1-dashboard-view'
}
expire_fragment.action_controller
{
key: 'posts/1-dashboard-view'
}
exist_fragment?.action_controller
{
key: 'posts/1-dashboard-view'
}
Action Dispatch
process_middleware.action_dispatch
redirect.action_dispatch
request.action_dispatch
Action View
render_template.action_view
키 |
값 |
:identifier |
템플릿의 전체 경로 |
:layout |
적용되는 레이아웃 |
:locals |
템플릿에 전달된 지역 변수 |
{
identifier: "/Users/adam/projects/notifications/app/views/posts/index.html.erb",
layout: "layouts/application",
locals: { foo: "bar" }
}
render_partial.action_view
키 |
값 |
:identifier |
템플릿의 전체 경로 |
:locals |
템플릿에 전달된 지역 변수 |
{
identifier: "/Users/adam/projects/notifications/app/views/posts/_form.html.erb",
locals: { foo: "bar" }
}
render_collection.action_view
키 |
값 |
:identifier |
템플릿의 네, 계속해서 번역하겠습니다. |
render_collection.action_view
키 |
값 |
:identifier |
템플릿의 전체 경로 |
:count |
컬렉션의 크기 |
:cache_hits |
캐시에서 가져온 부분 템플릿 수 |
:cache_hits
키는 컬렉션이 cached: true
로 렌더링된 경우에만 포함됩니다.
{
identifier: "/Users/adam/projects/notifications/app/views/posts/_post.html.erb",
count: 3,
cache_hits: 0
}
render_layout.action_view
키 |
값 |
:identifier |
템플릿의 전체 경로 |
{
identifier: "/Users/adam/projects/notifications/app/views/layouts/application.html.erb"
}
Active Record
sql.active_record
키 |
값 |
:sql |
SQL 문 |
:name |
작업 이름 |
:connection |
연결 객체 |
:binds |
바인딩 매개변수 |
:type_casted_binds |
유형 변환된 바인딩 매개변수 |
:statement_name |
SQL 문 이름 |
:async |
쿼리가 비동기적으로 로드되면 true |
:cached |
캐시된 쿼리가 사용되면 true 가 추가됨 |
:row_count |
쿼리에 의해 반환된 행 수 |
어댑터는 자체 데이터를 추가할 수 있습니다.
{
sql: "SELECT \"posts\".* FROM \"posts\" ",
name: "Post Load",
connection: <ActiveRecord::ConnectionAdapters::SQLite3Adapter:0x00007f9f7a838850>,
binds: [<ActiveModel::Attribute::WithCastValue:0x00007fe19d15dc00>],
type_casted_binds: [11],
statement_name: nil,
row_count: 5
}
strict_loading_violation.active_record
이 이벤트는 config.active_record.action_on_strict_loading_violation
가 :log
로 설정된 경우에만 발생합니다.
키 |
값 |
:owner |
strict_loading 이 활성화된 모델 |
:reflection |
로드하려고 시도한 연관 관계의 리플렉션 |
instantiation.active_record
키 |
값 |
:record_count |
인스턴스화된 레코드 수 |
:class_name |
레코드의 클래스 |
{
record_count: 1,
class_name: "User"
}
Action Mailer
deliver.action_mailer
키 |
값 |
:mailer |
메일러 클래스 이름 |
:message_id |
Mail 젬에 의해 생성된 메시지 ID |
:subject |
메일 제목 |
:to |
메일 수신자 주소 |
:from |
메일 발신자 주소 |
:bcc |
메일 숨은 참조 주소 |
:cc |
메일 참조 주소 |
:date |
메일 날짜 |
:mail |
메일의 인코딩된 형식 |
:perform_deliveries |
이 메시지 전송 수행 여부 |
{
mailer: "Notification",
message_id: "4f5b5491f1774_181b23fc3d4434d38138e5@mba.local.mail",
subject: "Rails Guides",
to: ["users@rails.com", "dhh@rails.com"],
from: ["me@rails.com"],
date: Sat, 10 Mar 2012 14:18:09 +0100,
mail: "...", # 간단히 생략
perform_deliveries: true
}
process.action_mailer
키 |
값 |
:mailer |
메일러 클래스 이름 |
:action |
작업 |
:args |
인수 |
{
mailer: "Notification",
action: "welcome_email",
args: []
}
Active Support: 캐싱
cache_read.active_support
키 |
값 |
:key |
저장소에서 사용된 키 |
:store |
저장소 클래스 이름 |
:hit |
이 읽기가 히트인지 여부 |
:super_operation |
fetch 로 읽기가 수행된 경우 :fetch |
cache_read_multi.active_support
키 |
값 |
:key |
저장소에서 사용된 키 |
:store |
저장소 클래스 이름 |
:hits |
캐시 히트 키 |
:super_operation |
fetch_multi 로 읽기가 수행된 경우 :fetch_multi |
cache_generate.active_support
이 이벤트는 fetch
가 블록과 함께 호출될 때만 발생합니다.
키 |
값 |
:key |
저장소에서 사용된 키 |
:store |
저장소 클래스 이름 |
fetch
에 전달된 옵션은 저장소에 쓸 때 페이로드에 병합됩니다.
{
key: "name-of-complicated-computation",
store: "ActiveSupport::Cache::MemCacheStore"
}
cache_fetch_hit.active_support
이 이벤트는 fetch
가 블록과 함께 호출될 때만 발생합니다.
키 |
값 |
:key |
저장소에서 사용된 키 |
:store |
저장소 클래스 이름 |
fetch
에 전달된 옵션은 페이로드에 병합됩니다.
{
key: "name-of-complicated-computation",
store: "ActiveSupport::Cache::MemCacheStore"
}
cache_write.active_support
키 |
값 |
:key |
저장소에서 사용된 키 |
:store |
저장소 클래스 이름 |
캐시 저장소는 자체 데이터를 추가할 수 있습니다.
{
key: "name-of-complicated-computation",
store: "ActiveSupport::Cache::MemCacheStore"
}
cache_write_multi.active_support
키 |
값 |
:key |
저장소에 쓰여진 키와 값 |
:store |
저장소 클래스 이름 |
cache_increment.active_support
이 이벤트는 MemCacheStore
또는 RedisCacheStore
를 사용할 때만 발생합니다.
키 |
값 |
:key |
저장소에서 사용된 키 |
:store |
저장소 클래스 이름 |
:amount |
증가 금액 |
{
key: "bottles-of-beer",
store: "ActiveSupport::Cache::RedisCacheStore",
amount: 99
}
cache_decrement.active_support
이 이벤트는 Memcached 또는 Redis 캐시 저장소를 사용할 때만 발생합니다.
키 |
값 |
:key |
저장소에서 사용된 키 |
:store |
저장소 클래스 이름 |
:amount |
감소 금액 |
{
key: "bottles-of-beer",
store: "ActiveSupport::Cache::RedisCacheStore",
amount: 1
}
cache_delete.active_support
키 |
값 |
:key |
저장소에서 사용된 키 |
:store |
저장소 클래스 이름 |
{
key: "name-of-complicated-computation",
store: "ActiveSupport::Cache::MemCacheStore"
}
cache_delete_multi.active_support
키 |
값 |
:key |
저장소에서 사용된 키 |
:store |
저장소 클래스 이름 |
cache_delete_matched.active_support
이 이벤트는 RedisCacheStore
, FileStore
또는 MemoryStore
를 사용할 때만 발생합니다.
키 |
값 |
:key |
사용된 키 패턴 |
:store |
저장소 클래스 이름 |
{
key: "posts/*",
store: "ActiveSupport::Cache::RedisCacheStore"
}
cache_cleanup.active_support
이 이벤트는 MemoryStore
를 사용할 때만 발생합니다.
키 |
값 |
:store |
저장소 클래스 이름 |
:size |
정리 전 캐시의 항목 수 |
{
store: "ActiveSupport::Cache::MemoryStore",
size: 9001
}
cache_prune.active_support
이 이벤트는 MemoryStore
를 사용할 때만 발생합니다.
키 |
값 |
:store |
저장소 클래스 이름 |
:key |
캐시의 대상 크기(바이트) |
:from |
정리 전 캐시의 크기(바이트) |
{
store: "ActiveSupport::Cache::MemoryStore",
key: 5000,
from: 9001
}
cache_exist?.active_support
키 |
값 |
:key |
저장소에서 사용된 키 |
:store |
저장소 클래스 이름 |
{
key: "name-of-complicated-computation",
store: "ActiveSupport::Cache::MemCacheStore"
}
Active Support: 메시지
message_serializer_fallback.active_support
키 |
값 |
:serializer |
기본(의도된) 직렬화기 |
:fallback |
대체(실제) 직렬화기 |
:serialized |
직렬화된 문자열 |
:deserialized |
역직렬화된 값 |
{
serializer: :json_allow_marshal,
fallback: :marshal네, 계속해서 번역하겠습니다.
### Active Support: 메시지
#### `message_serializer_fallback.active_support`
| 키 | 값 |
| --------------- | ----------------------------- |
| `:serializer` | 기본(의도된) 직렬화기 |
| `:fallback` | 대체(실제) 직렬화기 |
| `:serialized` | 직렬화된 문자열 |
| `:deserialized` | 역직렬화된 값 |
```ruby
{
serializer: :json_allow_marshal,
fallback: :marshal,
serialized: "\x04\b{\x06I\"\nHello\x06:\x06ETI\"\nWorld\x06;\x00T",
deserialized: { "Hello" => "World" },
}
Active Job
enqueue_at.active_job
키 |
값 |
:adapter |
작업을 처리하는 QueueAdapter 객체 |
:job |
작업 객체 |
enqueue.active_job
키 |
값 |
:adapter |
작업을 처리하는 QueueAdapter 객체 |
:job |
작업 객체 |
enqueue_retry.active_job
키 |
값 |
:job |
작업 객체 |
:adapter |
작업을 처리하는 QueueAdapter 객체 |
:error |
재시도를 유발한 오류 |
:wait |
재시도 지연 시간 |
enqueue_all.active_job
키 |
값 |
:adapter |
작업을 처리하는 QueueAdapter 객체 |
:jobs |
작업 객체 배열 |
키 |
값 |
:adapter |
작업을 처리하는 QueueAdapter 객체 |
:job |
작업 객체 |
키 |
값 |
:adapter |
작업을 처리하는 QueueAdapter 객체 |
:job |
작업 객체 |
:db_runtime |
데이터베이스 쿼리 실행에 소요된 시간(ms) |
retry_stopped.active_job
키 |
값 |
:adapter |
작업을 처리하는 QueueAdapter 객체 |
:job |
작업 객체 |
:error |
재시도를 중지시킨 오류 |
discard.active_job
키 |
값 |
:adapter |
작업을 처리하는 QueueAdapter 객체 |
:job |
작업 객체 |
:error |
작업 폐기를 유발한 오류 |
Action Cable
키 |
값 |
:channel_class |
채널 클래스 이름 |
:action |
작업 |
:data |
데이터 해시 |
transmit.action_cable
키 |
값 |
:channel_class |
채널 클래스 이름 |
:data |
데이터 해시 |
:via |
경로 |
transmit_subscription_confirmation.action_cable
키 |
값 |
:channel_class |
채널 클래스 이름 |
transmit_subscription_rejection.action_cable
키 |
값 |
:channel_class |
채널 클래스 이름 |
broadcast.action_cable
키 |
값 |
:broadcasting |
명명된 브로드캐스팅 |
:message |
메시지 해시 |
:coder |
코더 |
Active Storage
preview.active_storage
analyze.active_storage
키 |
값 |
:analyzer |
분석기 이름(예: ffprobe) |
Active Storage: 저장소 서비스
service_upload.active_storage
키 |
값 |
:key |
보안 토큰 |
:service |
서비스 이름 |
:checksum |
무결성 확인을 위한 체크섬 |
service_streaming_download.active_storage
키 |
값 |
:key |
보안 토큰 |
:service |
서비스 이름 |
service_download_chunk.active_storage
키 |
값 |
:key |
보안 토큰 |
:service |
서비스 이름 |
:range |
읽으려고 시도한 바이트 범위 |
service_download.active_storage
키 |
값 |
:key |
보안 토큰 |
:service |
서비스 이름 |
service_delete.active_storage
키 |
값 |
:key |
보안 토큰 |
:service |
서비스 이름 |
service_delete_prefixed.active_storage
키 |
값 |
:prefix |
키 접두사 |
:service |
서비스 이름 |
service_exist.active_storage
키 |
값 |
:key |
보안 토큰 |
:service |
서비스 이름 |
:exist |
파일 또는 blob이 존재하는지 |
service_url.active_storage
키 |
값 |
:key |
보안 토큰 |
:service |
서비스 이름 |
:url |
생성된 URL |
이 이벤트는 Google Cloud Storage 서비스를 사용할 때만 발생합니다.
키 |
값 |
:key |
보안 토큰 |
:service |
서비스 이름 |
:content_type |
HTTP Content-Type 필드 |
:disposition |
HTTP Content-Disposition 필드 |
Action Mailbox
process.action_mailbox
{
mailbox: #<RepliesMailbox:0x00007f9f7a8388>,
inbound_email: {
id: 1,
message_id: "0CB459E0-0336-41DA-BC88-E6E28C697DDB@37signals.com",
status: "processing"
}
}
Railties
load_config_initializer.railties
키 |
값 |
:initializer |
config/initializers 에 로드된 초기화기 경로 |
Rails
deprecation.rails
키 |
값 |
:message |
감사 경고 메시지 |
:callstack |
감사가 발생한 위치 |
:gem_name |
감사를 보고한 젬 이름 |
:deprecation_horizon |
제거될 예정인 동작 버전 |
예외
계측 중 예외가 발생하면 페이로드에 예외 정보가 포함됩니다.
키 |
값 |
:exception |
두 개의 요소로 이루어진 배열: 예외 클래스 이름과 메시지 |
:exception_object |
예외 객체 |
사용자 정의 이벤트 만들기
자체 이벤트를 추가하는 것도 쉽습니다. Active Support가 모든 복잡한 작업을 처리해 줍니다. ActiveSupport::Notifications.instrument
를 name
, payload
, 그리고 블록과 함께 호출하면 됩니다. 블록이 반환된 후 알림이 전송됩니다. Active Support는 시작 및 종료 시간을 생성하고 계측기의 고유 ID를 추가합니다. instrument
호출에 전달된 모든 데이터는 페이로드에 포함됩니다.
예:
ActiveSupport::Notifications.instrument "my.custom.event", this: :data do
# 사용자 정의 코드 작성
end
이제 다음과 같이 이 이벤트를 수신할 수 있습니다:
ActiveSupport::Notifications.subscribe "my.custom.event" do |name, started, finished, unique_id, data|
puts data.inspect # {:this=>:data}
end
블록 없이 instrument
를 호출할 수도 있습니다. 이렇게 하면 계측 인프라를 다른 메시징 용도로 활용할 수 있습니다.
ActiveSupport::Notifications.instrument "my.custom.event", this: :data
ActiveSupport::Notifications.subscribe "my.custom.event" do |name, started, finished, unique_id, data|
puts data.inspect # {:this=>:data}
end
사용자 정의 이벤트를 정의할 때는 Rails 규칙을 따르는 것이 좋습니다. 형식은 event.library
입니다. 애플리케이션이 트윗을 보내는 경우 tweet.twitter
와 같은 이벤트를 만들어야 합니다.