Add structured metadata to code evaluation logs

This will be easier to process from an external log aggregator. Useful when auditing code
evalution in an app server connected to a prod environment.
This commit is contained in:
Hugo Baraúna 2025-10-03 14:36:31 -03:00
parent aca9898625
commit 8ec644340f
4 changed files with 34 additions and 8 deletions

View file

@ -265,9 +265,13 @@ The following environment variables can be used to configure Livebook on boot:
logging, either of: error, warning, notice, info, debug. Defaults to warning.
* `LIVEBOOK_LOG_METADATA` - a comma-separated list of metadata keys that should
be included in the log messages. Currently the only Livebook-spcecific key is
users (attached to evaluation and request logs). By default includes only
request_id.
be included in the log messages. Livebook-specific keys include:
- `users` (attached to evaluation and request logs)
- `session_mode` (attached to evaluation logs, either "default" or "app")
- `code` (attached to evaluation logs, the code being evaluated)
- `event` (attached to evaluation logs, currently always "code.evaluate")
By default includes only `request_id`.
* `LIVEBOOK_LOG_FORMAT` - sets the log output format, either "text" (default)
for human-readable logs or "json" for structured JSON.

View file

@ -104,6 +104,8 @@ defmodule Livebook do
log_metadata = Livebook.Config.log_metadata!("LIVEBOOK_LOG_METADATA")
log_format = Livebook.Config.log_format!("LIVEBOOK_LOG_FORMAT") || :text
config :livebook, :log_format, log_format
case {log_format, log_metadata} do
{:json, log_metadata} ->
config :logger, :default_handler,

View file

@ -2685,8 +2685,20 @@ defmodule Livebook.Session do
end
def log_code_evaluation(cell, state) do
inspected_code = inspect(cell.source, printable_limit: :infinity)
# For metadata code, we try to use use the raw source, so it's easier to process
# the logged code in a external log aggregator.
metadata_code =
case cell.source do
source when is_binary(source) -> source
_ -> inspected_code
end
session_mode = state.data.mode
evaluation_users =
case state.data.mode do
case session_mode do
:default -> Map.values(state.data.users_map)
:app -> if(state.deployed_by, do: [state.deployed_by], else: [])
end
@ -2695,12 +2707,17 @@ defmodule Livebook.Session do
[
"""
Evaluating code
Session mode: #{state.data.mode}
Session mode: #{session_mode}
Code: \
""",
inspect(cell.source, printable_limit: :infinity)
inspected_code
],
Livebook.Utils.logger_users_metadata(evaluation_users)
Keyword.merge(
Livebook.Utils.logger_users_metadata(evaluation_users),
session_mode: session_mode,
code: metadata_code,
event: "code.evaluate"
)
)
end

View file

@ -854,6 +854,9 @@ defmodule Livebook.Utils do
into: %{}
end
[users: inspect(list)]
case Application.get_env(:livebook, :log_format) do
:text -> [users: inspect(list)]
:json -> [users: list]
end
end
end