diff --git a/packages/talker_dart_frog_logger/.gitignore b/packages/talker_dart_frog_logger/.gitignore new file mode 100644 index 000000000..3cceda557 --- /dev/null +++ b/packages/talker_dart_frog_logger/.gitignore @@ -0,0 +1,7 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ + +# Avoid committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/packages/talker_dart_frog_logger/CHANGELOG.md b/packages/talker_dart_frog_logger/CHANGELOG.md new file mode 100644 index 000000000..583d1cee3 --- /dev/null +++ b/packages/talker_dart_frog_logger/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1-dev.1 + +- Initial version. diff --git a/packages/talker_dart_frog_logger/LICENSE b/packages/talker_dart_frog_logger/LICENSE new file mode 100644 index 000000000..dffa734cc --- /dev/null +++ b/packages/talker_dart_frog_logger/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Stanislav Ilin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/talker_dart_frog_logger/README.md b/packages/talker_dart_frog_logger/README.md new file mode 100644 index 000000000..1fcddfd7d --- /dev/null +++ b/packages/talker_dart_frog_logger/README.md @@ -0,0 +1 @@ +Soon... \ No newline at end of file diff --git a/packages/talker_dart_frog_logger/analysis_options.yaml b/packages/talker_dart_frog_logger/analysis_options.yaml new file mode 100644 index 000000000..dee8927aa --- /dev/null +++ b/packages/talker_dart_frog_logger/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/packages/talker_dart_frog_logger/example/.gitignore b/packages/talker_dart_frog_logger/example/.gitignore new file mode 100644 index 000000000..67dfe6426 --- /dev/null +++ b/packages/talker_dart_frog_logger/example/.gitignore @@ -0,0 +1,16 @@ +# See https://www.dartlang.org/guides/libraries/private-files + +# Files and directories created by the Operating System +.DS_Store + +# Files and directories created by pub +.dart_tool/ +.packages +pubspec.lock + +# Files and directories created by dart_frog +build/ +.dart_frog + +# Test related files +coverage/ \ No newline at end of file diff --git a/packages/talker_dart_frog_logger/example/.vscode/extensions.json b/packages/talker_dart_frog_logger/example/.vscode/extensions.json new file mode 100644 index 000000000..be2e60ea6 --- /dev/null +++ b/packages/talker_dart_frog_logger/example/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["VeryGoodVentures.dart-frog"] +} \ No newline at end of file diff --git a/packages/talker_dart_frog_logger/example/README.md b/packages/talker_dart_frog_logger/example/README.md new file mode 100644 index 000000000..33e11980e --- /dev/null +++ b/packages/talker_dart_frog_logger/example/README.md @@ -0,0 +1,12 @@ +# example + +[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link] +[![License: MIT][license_badge]][license_link] +[![Powered by Dart Frog](https://img.shields.io/endpoint?url=https://tinyurl.com/dartfrog-badge)](https://dartfrog.vgv.dev) + +An example application built with dart_frog + +[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg +[license_link]: https://opensource.org/licenses/MIT +[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg +[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis \ No newline at end of file diff --git a/packages/talker_dart_frog_logger/example/analysis_options.yaml b/packages/talker_dart_frog_logger/example/analysis_options.yaml new file mode 100644 index 000000000..b9dcf40fc --- /dev/null +++ b/packages/talker_dart_frog_logger/example/analysis_options.yaml @@ -0,0 +1,9 @@ +include: package:very_good_analysis/analysis_options.5.1.0.yaml +analyzer: + errors: + inference_failure_on_instance_creation: ignore + exclude: + - build/** +linter: + rules: + file_names: false diff --git a/packages/talker_dart_frog_logger/example/pubspec.yaml b/packages/talker_dart_frog_logger/example/pubspec.yaml new file mode 100644 index 000000000..ebe52c5ae --- /dev/null +++ b/packages/talker_dart_frog_logger/example/pubspec.yaml @@ -0,0 +1,18 @@ +name: example +description: A new Dart Frog application +version: 1.0.0+1 +publish_to: none + +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + dart_frog: ^1.1.0 + talker_dart_frog_logger: + path: + ../ + +dev_dependencies: + mocktail: ^1.0.3 + test: ^1.25.5 + very_good_analysis: ^5.1.0 diff --git a/packages/talker_dart_frog_logger/example/routes/_middleware.dart b/packages/talker_dart_frog_logger/example/routes/_middleware.dart new file mode 100644 index 000000000..348091730 --- /dev/null +++ b/packages/talker_dart_frog_logger/example/routes/_middleware.dart @@ -0,0 +1,18 @@ +import 'package:dart_frog/dart_frog.dart'; +import 'package:talker_dart_frog_logger/talker_dart_frog_logger.dart'; + +Handler middleware(Handler handler) { + return (context) async { + /// Setup Talker logger only for middleware logs + final talker = Talker( + logger: TalkerLogger( + formatter: const DartFrogLoggerFormatter(), + ), + ); + + /// Or you can provide one talker instance via context + // final talker = context.read(); + + return loggerMiddleware(handler: handler, talker: talker)(context); + }; +} diff --git a/packages/talker_dart_frog_logger/example/routes/index.dart b/packages/talker_dart_frog_logger/example/routes/index.dart new file mode 100644 index 000000000..7d7710f6e --- /dev/null +++ b/packages/talker_dart_frog_logger/example/routes/index.dart @@ -0,0 +1,19 @@ +import 'dart:math'; + +import 'package:dart_frog/dart_frog.dart'; + +Future onRequest(RequestContext context) async { + await Future.delayed(Duration(milliseconds: Random().nextInt(1500))); + + return Response.json( + body: [ + { + 'name': 'talker_dart_frog_logger', + 'type': 'package', + 'tags': ['error-handler', 'logger', 'logs'], + 'rating': 5, + 'description': 'Best package in the world', + } + ], + ); +} diff --git a/packages/talker_dart_frog_logger/lib/src/logger.dart b/packages/talker_dart_frog_logger/lib/src/logger.dart new file mode 100644 index 000000000..2dfd60ce7 --- /dev/null +++ b/packages/talker_dart_frog_logger/lib/src/logger.dart @@ -0,0 +1,35 @@ +import 'package:dart_frog/dart_frog.dart'; +import 'package:talker/talker.dart'; + +import 'settings.dart'; +import 'talker_logs.dart'; + +Handler loggerMiddleware({ + required Handler handler, + required Talker talker, + TalkerDartFrogLoggerSettings settings = const TalkerDartFrogLoggerSettings(), +}) { + return (context) async { + if (settings.logRequest) { + final requestLog = RequestLog(context.request, settings: settings); + talker.logCustom(requestLog); + } + + final stopwatch = Stopwatch()..start(); + final response = await handler(context); + stopwatch.stop(); + + final time = stopwatch.elapsedMilliseconds; + + if (settings.logResponse) { + final responseLog = ResponseLog( + request: context.request, + response: response, + settings: settings, + resTime: time, + ); + talker.logCustom(responseLog); + } + return response; + }; +} diff --git a/packages/talker_dart_frog_logger/lib/src/logger_formatter.dart b/packages/talker_dart_frog_logger/lib/src/logger_formatter.dart new file mode 100644 index 000000000..b4a709a32 --- /dev/null +++ b/packages/talker_dart_frog_logger/lib/src/logger_formatter.dart @@ -0,0 +1,11 @@ +import 'package:talker/talker.dart'; + +class DartFrogLoggerFormatter implements LoggerFormatter { + const DartFrogLoggerFormatter(); + + @override + String fmt(LogDetails details, TalkerLoggerSettings settings) { + final msg = details.message?.toString() ?? ''; + return msg; + } +} diff --git a/packages/talker_dart_frog_logger/lib/src/settings.dart b/packages/talker_dart_frog_logger/lib/src/settings.dart new file mode 100644 index 000000000..afc755481 --- /dev/null +++ b/packages/talker_dart_frog_logger/lib/src/settings.dart @@ -0,0 +1,15 @@ +class TalkerDartFrogLoggerSettings { + const TalkerDartFrogLoggerSettings({ + this.logRequest = true, + this.logResponse = true, + this.printRequestHeaders = false, + this.printResponseHeaders = false, + this.printResponseBody = false, + }); + + final bool logRequest; + final bool logResponse; + final bool printRequestHeaders; + final bool printResponseHeaders; + final bool printResponseBody; +} diff --git a/packages/talker_dart_frog_logger/lib/src/talker_logs.dart b/packages/talker_dart_frog_logger/lib/src/talker_logs.dart new file mode 100644 index 000000000..bc7813c25 --- /dev/null +++ b/packages/talker_dart_frog_logger/lib/src/talker_logs.dart @@ -0,0 +1,80 @@ +import 'package:dart_frog/dart_frog.dart'; +import 'package:talker/talker.dart'; + +import 'settings.dart'; + +class RequestLog extends TalkerLog { + RequestLog(this.request, {required this.settings}) : super('req'); + + final Request request; + final TalkerDartFrogLoggerSettings settings; + + @override + String? get title => 'req'; + + @override + String generateTextMessage({ + TimeFormat timeFormat = TimeFormat.timeAndSeconds, + }) { + final sb = StringBuffer(); + sb.write(' ${displayTitleWithTime(timeFormat: timeFormat)}'); + sb.write('[${request.method.value}]'); + sb.write(' ${request.uri.path}'); + if (request.uri.query.isNotEmpty) { + sb.write('?${request.uri.query}'); + } + if (settings.printRequestHeaders && request.headers.isNotEmpty) { + sb.write('\n'); + for (final entry in request.headers.entries) { + sb.write('${entry.key}: ${entry.value}\n'); + } + } + return sb.toString(); + } +} + +class ResponseLog extends TalkerLog { + ResponseLog({ + required this.response, + required this.request, + required this.settings, + required this.resTime, + }) : super('res'); + + final Request request; + final Response response; + final TalkerDartFrogLoggerSettings settings; + final int resTime; + + @override + String? get title => 'res'; + + @override + String generateTextMessage({ + TimeFormat timeFormat = TimeFormat.timeAndSeconds, + }) { + final sb = StringBuffer(); + + sb.write(' ${displayTitleWithTime(timeFormat: timeFormat)}'); + sb.write('[${request.method.value}]'); + sb.write(' ${request.uri.path}'); + if (request.uri.query.isNotEmpty) { + sb.write('?${request.uri.query}'); + } + sb.write(' | ${response.statusCode}'); + sb.write(' | $resTime ms'); + + if (settings.printResponseHeaders && response.headers.isNotEmpty) { + sb.write('\n'); + for (final entry in response.headers.entries) { + sb.write('${entry.key}: ${entry.value}\n'); + } + } + + if (settings.printResponseBody) { + sb.write('\n'); + sb.write(response.body); + } + return sb.toString(); + } +} diff --git a/packages/talker_dart_frog_logger/lib/talker_dart_frog_logger.dart b/packages/talker_dart_frog_logger/lib/talker_dart_frog_logger.dart new file mode 100644 index 000000000..1f87c83af --- /dev/null +++ b/packages/talker_dart_frog_logger/lib/talker_dart_frog_logger.dart @@ -0,0 +1,7 @@ +library; + +export 'package:talker/talker.dart'; + +export 'src/logger.dart'; +export 'src/logger_formatter.dart'; +export 'src/settings.dart'; diff --git a/packages/talker_dart_frog_logger/pubspec.yaml b/packages/talker_dart_frog_logger/pubspec.yaml new file mode 100644 index 000000000..ff74ffdc0 --- /dev/null +++ b/packages/talker_dart_frog_logger/pubspec.yaml @@ -0,0 +1,15 @@ +name: talker_dart_frog_logger +description: Lightweight and customizable dart_frog logger on talker base +version: 0.0.1-dev.1 +repository: https://github.com/Frezyx/talker + +environment: + sdk: ">=2.19.0 <4.0.0" + +dependencies: + dart_frog: ^1.2.0 + talker: ^4.6.14 + +dev_dependencies: + lints: ^5.0.0 + test: ^1.24.0