From futuregerald-claude-plugin
Use when writing or modifying any Ruby code in cobalt repos. Enforces structured logging via SemanticLogger + Datadog so logs are queryable, dashboardable, and debuggable. Triggers on new interactors, jobs, services, controllers, error handling, and any business logic that should be observable.
How this skill is triggered — by the user, by Claude, or both
Slash command
/futuregerald-claude-plugin:cobalt-structured-loggingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Cobalt uses `rails_semantic_logger` with Datadog. Keyword arguments passed to `Rails.logger` become `metadata` fields in Datadog — filterable, facetable, and alertable. String interpolation buries data in unstructured text that Datadog cannot index.
Cobalt uses rails_semantic_logger with Datadog. Keyword arguments passed to Rails.logger become metadata fields in Datadog — filterable, facetable, and alertable. String interpolation buries data in unstructured text that Datadog cannot index.
Core rule: Every log statement must use the two-argument form: a short event name + keyword arguments for all variable data.
When writing or reviewing cobalt code: add structured logging where it aids debugging or observability — error rescues, business decisions, job outcomes, and external service calls. Don't add logging to every method by default; log when the information would help someone debugging a production issue. Flag any string-interpolated logs as needing upgrade. Verify error rescues include error_class and error_message fields.
Rails.logger.info(
'event_name',
key: value,
another_key: another_value,
)
domain.action or noun_verb_past stylemetadata in DatadogBusiness event:
Rails.logger.info(
'tpm_auto_assigned',
assignment_source: 'org_default',
selected_tpm: org.tpm.email,
engagement_token: engagement.token,
org_id: org.public_id.to_s,
)
Error with structured context:
Rails.logger.error(
'credential_validation.failed',
credential_id: credential.public_id,
error_class: e.class.name,
error_message: e.message,
)
Anti-pattern — string interpolation (unqueryable in Datadog):
# BAD
Rails.logger.info("Skipping schedule #{@schedule.public_id} - customer skipped next run")
# GOOD
Rails.logger.info('schedule.skipped', schedule_id: @schedule.public_id, reason: 'customer_skipped_next_run')
| Pattern | Example | Use for |
|---|---|---|
domain.action | sidekiq.health | Metrics/health checks |
noun_verb_past | tpm_auto_assigned | Business events |
LOG_TAG + description | [JobName] Succeeded | Jobs with multiple log points |
| Level | When |
|---|---|
info | Business events, successful operations, health checks |
warn | Degraded but recoverable, unexpected-but-handled conditions, authorization failures |
error | Failures that need attention, unrecoverable errors |
For jobs/services with multiple log points, use a constant tag and a shared payload method:
class SomeJob < ApplicationJob
LOG_TAG = '[SomeJob]'
def perform(token)
@record = Record.find_by(token: token)
return Rails.logger.warn("#{LOG_TAG} Record not found", token:) unless @record
result = SomeInteractor.call(record: @record)
if result.success?
Rails.logger.info("#{LOG_TAG} Succeeded", **log_payload, count: result.items.size)
else
Rails.logger.error("#{LOG_TAG} Failed", **log_payload, error: result.error)
end
end
private
def log_payload = { record_id: @record.public_id, record_token: @record.token }
end
Request-scoped context (url, ip, path, request_id, dd trace) is already added via config.log_tags in application.rb. You do not need to re-add these fields manually.
Use Rails.logger.tagged to push context down to all logs within a block — useful at the controller level, in organizers, or for batch operations:
# Controller-level tagging — all logs from this action (including interactors/services) get the tag
around_action :tag_logs
def tag_logs
Rails.logger.tagged(controller: self.class.name, action: action_name) { yield }
end
# Organizer/interactor wrapping
Rails.logger.tagged('Auth0') do
# all logs inside get the Auth0 tag
Auth0::SyncUser.call(user: user)
end
# Batch operations
Rails.logger.tagged(org_id: org.public_id) do
Rails.logger.info('batch_processing_started', batch_size: items.count)
end
Log when it aids debugging: business decisions (assignments, state transitions, skips), job success/failure, external service calls and outcomes, authorization denials, error recovery paths, scheduled/cron operations.
Don't log: routine happy-path reads, simple CRUD without business logic, sensitive data (passwords, tokens, PII beyond public_ids/emails), data already in request context (url, ip, request_id — these come from log_tags). Not every interactor or method needs logging — add it where the information would help someone investigating a production issue.
Only test log output when the log is the sole observable side effect of a code branch (e.g., an early return that only logs). Don't add specs for routine info logs — they create brittle tests without meaningful coverage.
# When a log is the only way to verify a branch was reached:
expect(Rails.logger).to receive(:warn).with(
'record_not_found',
hash_including(token: 'abc123'),
)
| Want to... | Do this |
|---|---|
| Log a business event | Rails.logger.info('event_name', field: value, ...) |
| Log a warning | Rails.logger.warn('event_name', field: value, ...) |
| Log an error with exception | Rails.logger.error('event_name', error_class: e.class.name, error_message: e.message, ...) |
| Add scoped context | Rails.logger.tagged(key: val) { ... } |
| Reuse context across log points | Define LOG_TAG constant + shared payload method |
| Test log (only if sole branch indicator) | expect(Rails.logger).to receive(:warn).with('event', hash_including(...)) |
npx claudepluginhub futuregerald/futuregerald-claude-pluginGuides collaborative design exploration before implementation: explores context, asks clarifying questions, proposes approaches, and writes a design doc for user approval.
Creates structured, bite-sized implementation plans from specs or requirements before writing code. Useful for breaking down multi-step tasks into testable steps with file structure and task boundaries.
Resolves in-progress git merge or rebase conflicts by analyzing history, understanding intent, and preserving both changes where possible. Runs automated checks after resolution.