axon_dart_frog 0.1.0 copy "axon_dart_frog: ^0.1.0" to clipboard
axon_dart_frog: ^0.1.0 copied to clipboard

Dart Frog middleware adapter for Axon: HTTP request logging for Dart servers. Wraps axon_core's token engine into idiomatic Dart Frog middleware using handler.use() and RequestContext.

axon_dart_frog #

pub package style: meragix

Dart Frog middleware adapter for Axon: HTTP request logging for Dart servers.


Installation #

dependencies:
  axon_dart_frog: ^0.1.0

Quick start #

In routes/_middleware.dart:

import 'package:dart_frog/dart_frog.dart';
import 'package:axon_dart_frog/axon_dart_frog.dart';

Handler middleware(Handler handler) {
  return handler.use(axonDartFrog());
}

Output:

GET /api/users 200 3 ms
POST /api/orders 201 12 ms
GET /api/users/99 404 1 ms

⚠️ Middleware order — read this first #

Dart Frog wraps middlewares like an onion. handler.use(A).use(B) means B is the outer laye: it runs last on the request phase and first on the response phase.

handler.use(A).use(B).use(C)

Request  phase: C → B → A → handler
Response phase: handler → A → B → C

Axon must always be the outermost layer (.use() called last) so it runs after all auth/session middlewares. This guarantees that DI values injected by inner middlewares are available via context.read<T>() when UserExtractor runs.

// ✅ CORRECT: Axon is outermost, auth runs first on request
Handler middleware(Handler handler) {
  return handler
      .use(authMiddleware())    // inner: injects user, runs first
      .use(axonDartFrog(...));  // outer: reads user, runs last
}

// ❌ WRONG: Axon runs before auth, context.read<User>() returns null
Handler middleware(Handler handler) {
  return handler
      .use(axonDartFrog(...))   // outer: runs first, DI not yet populated
      .use(authMiddleware());   // inner: too late
}

Configuration #

handler.use(axonDartFrog(
  config: AxonDartFrogConfig(
    format: LogFormats.dev,        // compiled once at startup
    serializer: null,              // null = auto (ColoredText or Text)
    sink: print,
    strict: true,
    skip: null,
    immediate: false,
    colorMode: ColorMode.auto,     // auto | always | never
    userExtractor: null,
  ),
))

Format strings #

Same as axon_shelf: all tokens from axon_core plus Dart Frog-specific ones.

// Predefined
handler.use(axonDartFrog(config: AxonDartFrogConfig(format: LogFormats.combined)))

// Custom
handler.use(axonDartFrog(config: AxonDartFrogConfig(
  format: ':remote-addr :method :url :status :response-time.ms ms',
)))

Filtering with skip #

// Log only errors
handler.use(axonDartFrog(config: AxonDartFrogConfig(
  skip: SkipPredicates.successfulRequests,
)))

// Suppress health checks
handler.use(axonDartFrog(config: AxonDartFrogConfig(
  skip: SkipPredicates.paths({'/health', '/ready', '/ping'}),
)))

// Custom
handler.use(axonDartFrog(config: AxonDartFrogConfig(
  skip: (ctx) => ctx.request.method == 'OPTIONS',
)))

Serializers #

Text (default, auto-colored) #

handler.use(axonDartFrog(config: AxonDartFrogConfig(
  colorMode: ColorMode.always,   // force colors in CI
)))

JSON - ELK / Datadog / Loki #

handler.use(axonDartFrog(config: AxonDartFrogConfig(
  serializer: JsonSerializer(
    extraFields: {'service': 'my-api', 'env': 'production'},
  ),
  sink: myStructuredLogger.info,
)))

Immediate mode #

Log at request arrival before the handler runs:

handler.use(axonDartFrog(config: AxonDartFrogConfig(immediate: true)))

Response fields (:status, :response-time, :content-length) render as -. Useful when the server may crash before sending a response.


Authenticated user: :remote-user #

Dart Frog's DI system (context.read<T>()) is the idiomatic way to pass the authenticated user from an auth middleware to the logger. Use DartFrogRequestSnapshotX to access the RequestContext inside the UserExtractor:

Handler middleware(Handler handler) {
  return handler
      // 1. Auth middleware provides the user
      .use(_jwtMiddleware())
      // 2. Axon reads it via DI: order matters
      .use(axonDartFrog(
        config: AxonDartFrogConfig(
          format: ':remote-addr - :remote-user [:date.clf] ":method :url" :status',
          skip: SkipPredicates.paths({'/health'}),
          userExtractor: (logCtx) {
            final reqCtx = logCtx.request.dartFrogContext;
            return reqCtx?.read<AuthUser?>()?.id;
          },
        ),
      ));
}

Auth middleware example:

Middleware _jwtMiddleware() {
  return (Handler handler) {
    return (RequestContext context) async {
      final token = context.request.headers['authorization']
          ?.replaceFirst('Bearer ', '');
      final user = token != null ? await verifyJwt(token) : null;
      return handler(context.provide<AuthUser?>(() => user));
    };
  };
}

Output with authenticated user:

203.0.113.42 - john_doe [03/Apr/2026:10:00:00 +0000] "GET /api/orders" 200
203.0.113.42 - -        [03/Apr/2026:10:00:00 +0000] "GET /api/public" 200

Other UserExtractor patterns #

// From request header (API key, reverse proxy injection)
userExtractor: (logCtx) => logCtx.request.headers['x-user-id'],

// From response header (auth gateway pattern)
userExtractor: (logCtx) => logCtx.response?.headers['x-authenticated-as'],

// Dart Frog auth package (dart_frog_auth)
userExtractor: (logCtx) =>
    logCtx.request.dartFrogContext?.read<User?>()?.email,


Scoped logging per route directory #

Dart Frog supports per-directory middleware. Use this to apply different log configs to different route groups:

routes/
├── _middleware.dart        ← global: combined format, JSON to Datadog
├── api/
│   └── _middleware.dart   ← API routes: errors only
└── admin/
    └── _middleware.dart   ← Admin routes: full CLF + user identity
// routes/api/_middleware.dart
Handler middleware(Handler handler) {
  return handler.use(axonDartFrog(
    config: AxonDartFrogConfig(
      skip: SkipPredicates.successfulRequests,
    ),
  ));
}

// routes/admin/_middleware.dart
Handler middleware(Handler handler) {
  return handler.use(axonDartFrog(
    config: AxonDartFrogConfig(
      format: LogFormats.combined,
      userExtractor: (logCtx) =>
      logCtx.request.dartFrogContext?.read<AdminUser?>()?.username,
    ),
  ));
}

Dart Frog-specific tokens #

Registered automatically by axonDartFrog():

Token Description
:remote-addr Client IP from X-Forwarded-For or X-Real-IP
:referrer Referer / Referrer header
:user-agent User-Agent header
:remote-user Via UserExtractor — reads from RequestContext DI

Dart Frog vs Shelf #

axon_shelf axon_dart_frog
Handler type Request → Response RequestContext → Response
DI access Via ShelfRequestSnapshotX.shelfContext Via DartFrogRequestSnapshotX.dartFrogContext
User injection request.change(context: {'user': ...}) context.provide<User>(() => user)
:remote-addr From shelf.io.connection_info From X-Forwarded-For / X-Real-IP

Error handling #

axonDartFrog logs even when the handler throws. A synthetic 500 is recorded and the original error is re-thrown with its stack trace preserved.



License #

MIT — see LICENSE.

Part of the Axon ecosystem by Meragix.

0
likes
140
points
38
downloads

Documentation

API reference

Publisher

verified publishermeragix.dev

Weekly Downloads

Dart Frog middleware adapter for Axon: HTTP request logging for Dart servers. Wraps axon_core's token engine into idiomatic Dart Frog middleware using handler.use() and RequestContext.

Homepage
Repository (GitHub)
View/report issues

Topics

#logging #http-logger #dart-frog #server-side-dart

License

MIT (license)

Dependencies

axon_core, dart_frog

More

Packages that depend on axon_dart_frog