moon-accesslog

    A Common and Combined Log Format analyzer for MoonBit

    nginx
    apache
    access-log
    log-analysis
    cli
    Download zip
    Author
    Version
    0.4.1
    License
    Apache-2.0
    Last updated
    11 days ago
    Downloads
    38

    No dependencies

    #moon-accesslog

    moon-accesslog is a dependency-free MoonBit analyzer for Nginx and Apache Common/Combined access logs. It parses quoted request fields correctly and turns a large log into a compact operational summary: requests, transferred bytes, status and method counts, top paths, client addresses, and UTC-hour traffic.

    #Install

    The module is published on mooncakes.io:

    moon add pei0331/moon-accesslog

    Add the package to a moon.pkg file with the @accesslog alias:

    import { "pei0331/moon-accesslog" @accesslog, }

    #Quick start

    moon run cmd/main -- examples/access.log moon run cmd/main -- --top 5 examples/access.log moon run cmd/main -- --json examples/access.log moon run cmd/main -- --csv examples/access.log moon run cmd/main -- --method GET --path-prefix /api examples/access.log moon run cmd/main -- --status-min 400 --status-max 599 --json examples/access.log moon run cmd/main -- --max-error-rate 5 examples/access.log moon run cmd/main -- --hour-prefix 23/Sep/2026:09 examples/access.log moon run cmd/main -- --prometheus examples/access.log moon run cmd/main -- --markdown --top 5 examples/access.log moon run cmd/main -- --ndjson examples/access.log

    Malformed records are counted and skipped, while valid lines still contribute to the report. The first release loads the selected file into memory before analysis; it is intended for operational log files, not unbounded streams.

    #Library API

    let report = @accesslog.analyze(source)
    println(report.to_text(top=10))

    For a filtered library report:

    ///|
    let options = @accesslog.AnalyzeOptions::new().with_hour_prefix(
    Some("23/Sep/2026:09"),
    )

    ///|
    let report = @accesslog.analyze_with_options(source, options)

    parse_line accepts Common and Combined Log Format entries and returns a structured record. analyze aggregates records without making network calls or depending on a server runtime.

    Report::to_json() emits deterministic JSON with totals and sorted count maps for status codes, methods, paths, client addresses and UTC-hour buckets. The CLI's --json flag selects the same machine-readable format for archival and CI pipelines; --top remains available for the human-readable report.

    Report::to_csv() and the CLI's --csv option emit a header plus normalized metric,key,value rows. Values are escaped according to CSV rules, and the same stable ordering is used on every run.

    Reports also expose error_requests(), error_rate_percent() and unique_path_count() for health checks and dashboards. Error requests include both client and server failures (HTTP status 400 and above); the percentage is rounded down to a whole number. Report::is_healthy(max_error_rate_percent) provides the same inclusive budget check for library callers. The CLI's --max-error-rate N exits with an error when the analyzed report exceeds that budget, making it suitable for CI gates.

    AnalyzeOptions::with_hour_prefix(...) and --hour-prefix PREFIX select a UTC hour or day prefix using the native access-log timestamp format, which makes repeatable time-window checks possible without locale or timezone dependencies.

    Filtering is available without changing the source data. The CLI accepts --method, --path-prefix, --address, --status-min and --status-max; filtered valid records are reported separately as filtered_lines. The library equivalent is AnalyzeOptions::from(...) plus analyze_with_options(...).

    Every record exposes both its original request target (path) and the route with the query string removed (route). Reports include route counts, HTTP status-class counts, success/client-error/server-error metrics, and structured top_paths, top_routes and top_addresses results. Analyzer::push and analyze_lines support incremental or pre-split input for callers that do not want to assemble one large source string.

    Reports also aggregate User-Agent, referrer, protocol and query parameter names. Operational APIs include slo_snapshot(), findings(), endpoint_summaries(), top_error_routes(), largest_routes() and within_budgets(...). Exporters cover Prometheus text exposition (to_prometheus), Markdown (to_markdown), line-oriented NDJSON (to_ndjson), environment key/value snapshots (to_env) and compact health JSON (to_summary_json).

    #Development

    moon fmt --check moon info moon check moon test moon run cmd/main -- examples/access.log moon run cmd/main -- --json examples/access.log moon run cmd/main -- --status-min 400 examples/access.log moon run cmd/main -- --prometheus examples/access.log moon build --target native

    The test suite covers malformed records, quoted fields, query/route handling, filter accounting, deterministic output ordering, incremental analysis and status health metrics, time-prefix filtering, malformed-field rejection and operational exporter output.

    #License

    Apache-2.0. See LICENSE.

    AccessRecord

    pub struct AccessRecord {
    address : String
    timestamp : String
    http_method : String
    path : String
    route : String
    query : String
    protocol : String
    status : Int
    bytes : Int
    referrer : String
    user_agent : String
    }

    One parsed Common or Combined Log Format record.

    AnalyzeOptions

    pub struct AnalyzeOptions {
    http_method : String?
    path_prefix : String?
    address : String?
    status_min : Int?
    status_max : Int?
    hour_prefix : String?
    }

    Optional predicates applied while analyzing access log records.

    AnalyzeOptions::from

    fn AnalyzeOptions::from(http_method : String?, path_prefix : String?, address : String?, status_min : Int?, status_max : Int?) -> AnalyzeOptions

    Build filters for a method, route prefix, address and inclusive status range.

    AnalyzeOptions::new

    Create an analyzer with no filters.

    AnalyzeOptions::with_hour_prefix

    fn AnalyzeOptions::with_hour_prefix(self : AnalyzeOptions, hour_prefix : String?) -> AnalyzeOptions

    Restrict analysis to records whose timestamp starts with a UTC hour prefix.

    Analyzer

    pub struct Analyzer {
    options : AnalyzeOptions
    report : Report
    }

    A functional incremental analyzer suitable for chunked readers.

    Analyzer::finish

    fn Analyzer::finish(self : Analyzer) -> Report

    Finish an incremental analysis and return its report.

    Analyzer::new

    fn Analyzer::new(options : AnalyzeOptions) -> Analyzer

    Create an incremental analyzer.

    Analyzer::push

    fn Analyzer::push(self : Analyzer, line : String) -> Analyzer

    Consume one line and return the updated analyzer.

    EndpointSummary

    pub struct EndpointSummary {
    route : String
    requests : Int
    errors : Int
    total_bytes : Int
    error_rate_percent : Int
    average_bytes : Int
    depth : Int
    }

    A compact route-level summary for dashboards and endpoint reviews.

    MetricFinding

    pub struct MetricFinding {
    code : String
    severity : String
    message : String
    value : Int
    }

    A ranked operational finding derived from a report.

    Report

    pub struct Report {
    total_lines : Int
    invalid_lines : Int
    filtered_lines : Int
    requests : Int
    total_bytes : Int
    query_requests : Int
    status_counts : Map[Int, Int]
    method_counts : Map[String, Int]
    path_counts : Map[String, Int]
    address_counts : Map[String, Int]
    hourly_counts : Map[String, Int]
    route_counts : Map[String, Int]
    user_agent_counts : Map[String, Int]
    referrer_counts : Map[String, Int]
    query_key_counts : Map[String, Int]
    protocol_counts : Map[String, Int]
    route_error_counts : Map[String, Int]
    route_bytes_counts : Map[String, Int]
    }

    Aggregate report for valid and malformed log lines.

    Report::average_bytes

    fn Report::average_bytes(self : Report) -> Int

    Return the average response size in bytes.

    Report::client_error_requests

    fn Report::client_error_requests(self : Report) -> Int

    Number of client failures (4xx).

    Report::endpoint_summaries

    fn Report::endpoint_summaries(self : Report, limit : Int) -> Array[EndpointSummary]

    Return a stable route-level summary sorted by request count.

    Report::error_rate_percent

    fn Report::error_rate_percent(self : Report) -> Int

    Error percentage rounded down to a whole number.

    Report::error_requests

    fn Report::error_requests(self : Report) -> Int

    Number of valid requests with a 4xx or 5xx status code.

    Report::findings

    fn Report::findings(self : Report) -> Array[MetricFinding]

    Find common operational problems in a report.

    Report::invalid_rate_percent

    fn Report::invalid_rate_percent(self : Report) -> Int

    Return the ratio of malformed lines among all non-empty input lines.

    Report::is_healthy

    fn Report::is_healthy(self : Report, max_error_rate_percent : Int) -> Bool

    Return whether the report is within an inclusive error-rate budget.

    Report::largest_routes

    fn Report::largest_routes(self : Report, limit : Int) -> Array[(String, Int)]

    Return routes ranked by transferred bytes.

    Report::merge

    fn Report::merge(self : Report, other : Report) -> Report

    Combine two reports produced with the same analysis options.

    Report::query_request_percent

    fn Report::query_request_percent(self : Report) -> Int

    Return the percentage of requests carrying a query string.

    Report::request_class_counts

    fn Report::request_class_counts(self : Report) -> Map[String, Int]

    Count requests by broad operational class.

    Report::route_depth_counts

    fn Report::route_depth_counts(self : Report) -> Map[Int, Int]

    Count requests by route depth, where /a/b has depth two.

    Report::route_depth_percent

    fn Report::route_depth_percent(self : Report, depth : Int) -> Int

    Return the percentage of requests at one route depth.

    Report::server_error_requests

    fn Report::server_error_requests(self : Report) -> Int

    Number of server failures (5xx and above).

    Report::slo_snapshot

    fn Report::slo_snapshot(self : Report) -> SloSnapshot

    Return the report's core service-level indicators.

    Report::status_class_counts

    fn Report::status_class_counts(self : Report) -> Map[String, Int]

    Return counts grouped into HTTP status classes such as 2xx and 5xx.

    Report::status_class_percent

    fn Report::status_class_percent(self : Report, class : String) -> Int

    Return the percentage represented by one status class.

    Report::success_rate_percent

    fn Report::success_rate_percent(self : Report) -> Int

    Successful request percentage rounded down to a whole number.

    Report::success_requests

    fn Report::success_requests(self : Report) -> Int

    Number of successful (2xx and 3xx) requests.

    Report::to_csv

    fn Report::to_csv(self : Report) -> String

    Render metrics as CSV rows with the columns metric,key,value.

    Report::to_env

    fn Report::to_env(self : Report, prefix : String) -> String

    Render a compact environment-style key/value snapshot.

    Report::to_json

    fn Report::to_json(self : Report) -> String

    Render the report as deterministic JSON for archival and CI pipelines.

    Report::to_markdown

    fn Report::to_markdown(self : Report, top? : Int) -> String

    Render a compact Markdown report for issue comments and release notes.

    Report::to_ndjson

    fn Report::to_ndjson(self : Report) -> String

    Render one JSON object per request dimension for line-oriented pipelines.

    Report::to_prometheus

    fn Report::to_prometheus(self : Report, prefix : String) -> String

    Render a Prometheus text exposition with stable metric names.

    Report::to_summary_json

    fn Report::to_summary_json(self : Report) -> String

    Render a deterministic JSON object containing only top-level health data.

    Report::to_text

    fn Report::to_text(self : Report, top? : Int) -> String

    Render the report as stable plain text.

    Report::top_addresses

    fn Report::top_addresses(self : Report, limit : Int) -> Array[(String, Int)]

    Return the most frequent client addresses.

    Report::top_error_routes

    fn Report::top_error_routes(self : Report, limit : Int) -> Array[(String, Int)]

    Return routes ranked by error count, then route name.

    Report::top_paths

    fn Report::top_paths(self : Report, limit : Int) -> Array[(String, Int)]

    Return the most frequent paths, sorted by count then path.

    Report::top_protocols

    fn Report::top_protocols(self : Report, limit : Int) -> Array[(String, Int)]

    Return protocol usage counts ranked by frequency.

    Report::top_query_keys

    fn Report::top_query_keys(self : Report, limit : Int) -> Array[(String, Int)]

    Return query parameter names, independent of their values.

    Report::top_referrers

    fn Report::top_referrers(self : Report, limit : Int) -> Array[(String, Int)]

    Return the most frequent referrer values.

    Report::top_routes

    fn Report::top_routes(self : Report, limit : Int) -> Array[(String, Int)]

    Return the most frequent routes with query strings removed.

    Report::top_user_agents

    fn Report::top_user_agents(self : Report, limit : Int) -> Array[(String, Int)]

    Return the most frequent User-Agent values.

    Report::unhealthy_route_count

    fn Report::unhealthy_route_count(self : Report, threshold_percent : Int) -> Int

    Return the number of routes whose error rate exceeds a threshold.

    Report::unique_path_count

    fn Report::unique_path_count(self : Report) -> Int

    Number of distinct request paths in valid records.

    Report::unique_protocol_count

    fn Report::unique_protocol_count(self : Report) -> Int

    Return the number of protocols observed.

    Report::unique_query_key_count

    fn Report::unique_query_key_count(self : Report) -> Int

    Return the number of distinct query parameter names.

    Report::unique_referrer_count

    fn Report::unique_referrer_count(self : Report) -> Int

    Return the number of distinct referrers.

    Report::unique_user_agent_count

    fn Report::unique_user_agent_count(self : Report) -> Int

    Return the number of distinct user agents.

    Report::within_budgets

    fn Report::within_budgets(self : Report, max_error_rate_percent : Int, max_invalid_rate_percent : Int) -> Bool

    Check both an error-rate and malformed-input budget.

    SloSnapshot

    pub struct SloSnapshot {
    availability_percent : Int
    error_budget_percent : Int
    invalid_input_percent : Int
    query_request_percent : Int
    total_requests : Int
    server_errors : Int
    }

    A service-level objective snapshot derived from one report.

    analyze

    fn analyze(source : String) -> Report

    Analyze newline-separated Common or Combined Log Format input.

    analyze_lines

    fn analyze_lines(lines : Array[String], options : AnalyzeOptions) -> Report

    Analyze an array of already split lines.

    analyze_with_options

    fn analyze_with_options(source : String, options : AnalyzeOptions) -> Report

    Analyze input while applying optional method, route, address and status filters.

    parse_line

    fn parse_line(line : String) -> AccessRecord?

    Parse one Common or Combined Log Format record.