Packages

An Elixir utiliy to filter logs

Current section

Files

Jump to
telemetry_data lib mix tasks log.export.ex
Raw

lib/mix/tasks/log.export.ex

defmodule Mix.Tasks.Log.Export do
@moduledoc """
Export parsed log entries to JSON or CSV.
## Usage
mix log.export <path> [options]
## Options
--to Output format: json or csv (default: json)
--output Write to file instead of stdout
--level Filter by log level
--format Filter by log format (morgan, java, elixir, nginx)
--grep Filter by message content
## Examples
mix log.export logs/app.log
mix log.export logs/app.log --to csv --output report.csv
mix log.export logs/ --level error --to json --output errors.json
mix log.export logs/app.log --to csv | head -5
"""
@shortdoc "Export log entries to JSON or CSV"
use Mix.Task
alias TelemetryData.{FileReader, Exporter}
@impl Mix.Task
def run(args) do
{opts, positional, _} =
OptionParser.parse(args,
strict: [to: :string, output: :string, level: :string, format: :string, grep: :string]
)
path = List.first(positional) || "logs/"
export_format = parse_export_format(Keyword.get(opts, :to, "json"))
output_path = opts[:output]
entries = load_entries(path) |> apply_filters(opts)
if output_path do
case Exporter.write_file(entries, output_path, export_format) do
:ok ->
Mix.shell().info("Exported #{length(entries)} entries to #{output_path}")
{:error, reason} ->
Mix.raise("Failed to write #{output_path}: #{reason}")
end
else
content =
case export_format do
:json -> Exporter.to_json(entries)
:csv -> Exporter.to_csv(entries)
end
IO.puts(content)
end
end
defp parse_export_format("json"), do: :json
defp parse_export_format("csv"), do: :csv
defp parse_export_format(other), do: Mix.raise("Unknown format: #{other}. Use json or csv.")
defp load_entries(path) do
if File.dir?(path) do
case FileReader.read_dir(path) do
{:ok, entries} -> entries
{:error, reason} -> Mix.raise("Failed to read directory #{path}: #{reason}")
end
else
case FileReader.read_file(path) do
{:ok, entries} -> entries
{:error, reason} -> Mix.raise("Failed to read file #{path}: #{reason}")
end
end
end
defp apply_filters(entries, opts) do
entries
|> maybe_filter(:level, opts[:level])
|> maybe_filter(:format, opts[:format])
|> maybe_filter(:grep, opts[:grep])
end
defp maybe_filter(entries, _key, nil), do: entries
defp maybe_filter(entries, :level, level) do
Enum.filter(entries, &(&1.level == String.downcase(level)))
end
defp maybe_filter(entries, :format, format) do
Enum.filter(entries, &(&1.format == String.to_existing_atom(format)))
end
defp maybe_filter(entries, :grep, pattern) do
pattern_down = String.downcase(pattern)
Enum.filter(entries, &String.contains?(String.downcase(&1.message), pattern_down))
end
end