API Design: Typed vs. Untyped
LogStruct is designed for an idiomatic Rails experience first, with an optional typed path for teams that use sorbet-runtime. Most Rails developers can adopt LogStruct without learning Sorbet. Teams wanting stronger guarantees can progressively introduce typed log structs.
Untyped, Idiomatic Rails
You can continue using Rails.logger
with hashes and strings. LogStruct's formatter scrubs sensitive values and keeps output JSON-friendly.
# Untyped, idiomatic Rails logging (works out of the box)
Rails.logger.info({
msg: "User signed in",
user_id: current_user.id,
feature: "onboarding"
})
Optional Typed Path
For teams that want stricter contracts, use LogStruct's typed structs. These are runtime-checked via sorbet-runtime and integrate with our formatter seamlessly.
# Optional typed API (sorbet-runtime)
log = LogStruct::Log::Request.new(
message: "GET /projects",
event: LogStruct::Event::Request,
source: LogStruct::Source::Rails,
controller: "ProjectsController",
action: "index"
)
LogStruct.info(log)
Custom Typed Structures (Sketch)
You can define app-specific typed logs by composing LogStruct interfaces. Keep this ergonomic and discoverable; the untyped path remains first-class.
# Sketch: defining a custom typed log struct
class MyApp::Logs::Checkout < T::Struct
include LogStruct::Log::Interfaces::CommonFields
include LogStruct::Log::Interfaces::AdditionalDataField
const :event, LogStruct::Event::Log
const :source, LogStruct::Source, default: T.let(LogStruct::Source::App, LogStruct::Source)
const :message, String
const :cart_id, String
const :amount_cents, Integer
end
# Then log it with
LogStruct.info(
MyApp::Logs::Checkout.new(message: "checkout_completed", cart_id: cart.id, amount_cents: 1299)
)