Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
# flutter_remote_logging
Sending [Logger()](https://pub.dev/packages/logging) messages to loggly.com
# Dart package `remote_logging`

```dart
Sending [Logger()](https://pub.dev/packages/logging) messages to various remote logging services,
like [Loggly](https://www.loggly.com/docs/http-endpoint/), [Splunk](https://docs.splunk.com/Documentation/Splunk/latest/Data/UsetheHTTPEventCollector), etc.

```dart
import 'package:remote_logging/remote_logging.dart';

void main() async {
initLogging(logglyToken, verboseLoggers: ['logger1', 'logger2'], tagsProvider: (record) {
return [
'tag1',
'tag2',
];
});
final logglyCollector = LogglyCollector(logglyToken);
initRemoteLogging(
logglyCollector,
verboseLoggers: ['logger1', 'logger2'],
tagsProvider: (_) => ['tag1','tag2'],
);

// https://pub.dev/packages/logging
Logger('logger1').info('info on logger1');
Logger('logger2').info('info on logger2');
Expand Down
6 changes: 3 additions & 3 deletions lib/remote_logging.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/// Created by alex@justprodev.com on 27.05.2022.

library remote_logging;
library;

export 'package:logging/logging.dart';

export 'src/model.dart';
export 'src/remote/collectors/loggly_collector.dart';
export 'src/remote/collectors/splunk_collector.dart';
export 'src/remote_logging.dart';
export 'src/remote/loggly.dart' show logglyTasks;

8 changes: 7 additions & 1 deletion lib/src/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,10 @@ import 'package:logging/logging.dart';

/// Created by alex@justprodev.com on 27.05.2022.

typedef TagsProvider = List<String> Function(LogRecord record);
/// Function that provides tags for a log record
typedef TagsProvider = List<String> Function(LogRecord record);

/// Collects logs and sends them to some remote service
abstract class LogCollector {
Future<void> collect(String message, {List<String>? tags});
}
15 changes: 15 additions & 0 deletions lib/src/remote/collectors/http_collector.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Created by alex@justprodev.com on 20.12.2025.

import 'package:http/http.dart';
import 'package:remote_logging/src/model.dart';

/// Base class for HTTP log collectors
abstract class HttpCollector implements LogCollector {
late final Client client;
final Uri url;
final Map<String, String> headers;

HttpCollector({required this.url, this.headers = const {}, Client? client}) {
this.client = client ?? Client();
}
}
30 changes: 30 additions & 0 deletions lib/src/remote/collectors/loggly_collector.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Created by alex@justprodev.com on 27.05.2022.

import 'http_collector.dart';

/// Loggly log collector
/// See: https://www.loggly.com/docs/http-endpoint/
class LogglyCollector extends HttpCollector {
LogglyCollector(
String token, {
host = defaultHost,
super.client,
}) : super(
url: Uri.parse('https://$host/inputs/$token'),
headers: {'Content-type': 'text/plain; charset=utf-8'},
);

@override
Future<void> collect(String message, {List<String>? tags}) {
return client.post(
url,
body: message,
headers: {
...headers,
'X-LOGGLY-TAG': tags?.join(',') ?? '',
},
);
}

static const defaultHost = 'logs-01.loggly.com';
}
28 changes: 28 additions & 0 deletions lib/src/remote/collectors/splunk_collector.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Created by alex@justprodev.com on 20.12.2025.

import 'dart:convert';
import 'http_collector.dart';

/// Splunk log collector
///
/// See: https://docs.splunk.com/Documentation/Splunk/latest/Data/UsetheHTTPEventCollector
class SplunkCollector extends HttpCollector {
SplunkCollector(String token, {required String host, super.client})
: super(
url: Uri.parse('https://$host/services/collector/event'),
headers: {
'Authorization': 'Splunk $token',
'Content-Type': 'application/json;charset=utf-8',
},
);

@override
Future<void> collect(String message, {List<String>? tags}) {
final data = {
'event': message,
if (tags != null && tags.isNotEmpty) 'fields': {'tags': tags},
};

return client.post(url, body: jsonEncode(data), headers: headers);
}
}
26 changes: 0 additions & 26 deletions lib/src/remote/loggly.dart

This file was deleted.

17 changes: 0 additions & 17 deletions lib/src/remote/mobile_tags.dart

This file was deleted.

3 changes: 3 additions & 0 deletions lib/src/remote/tags/impl/empty_tags.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Created by alex@justprodev.com on 19.12.2025.

final List<String> defaultTags = [];
5 changes: 5 additions & 0 deletions lib/src/remote/tags/impl/io_tags.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import 'dart:io' show Platform;

/// Created by alex@justprodev.com on 23.05.2023.

final List<String> defaultTags = [Platform.operatingSystem];
File renamed without changes.
6 changes: 6 additions & 0 deletions lib/src/remote/tags/tags.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Created by alex@justprodev.com on 19.12.2025.

// export by platform
export 'impl/empty_tags.dart'
if (dart.library.io) 'impl/io_tags.dart'
if (dart.library.html) 'impl/web_tags.dart';
76 changes: 60 additions & 16 deletions lib/src/remote_logging.dart
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
// Created by alex@justprodev.com on 27.05.2022.

import 'dart:async';

import 'package:logging/logging.dart';
import 'package:remote_logging/src/model.dart';
import 'package:remote_logging/src/remote/tags/tags.dart';

import 'remote/collectors/loggly_collector.dart' show LogglyCollector;

import 'remote/loggly.dart';
/// Non-completed tasks
final Set<Future> tasks = {};

///
/// Watch the root [Logger] and then send messages addressed [verboseLoggers] to loggly
/// [verboseLoggers] name of loggers that will be sent to loggly verbosely - i.e. INFO messages, etc
/// Watch the root [Logger] and then send messages addressed [verboseLoggers] to [collectors]
/// [verboseLoggers] name of loggers that will be sent to [collectors] verbosely - i.e. INFO messages, etc
/// [tagsProvider] tags for loggly
/// [printToConsole] print message with [debugPrint]
void initLogging(
String logglyToken, {
/// [output] passes all messages to this function (e.g., for printing to console in custom way)
/// [preProcess] pre-process message before sending to collectors (e.g., hide sensitive info)
/// [includeStackTrace] include stack trace in the message sent to collectors
void initRemoteLogging(
LogCollector collector, {
List<String>? verboseLoggers,
TagsProvider? tagsProvider,
bool Function()? printToConsole,
Function(LogRecord, String)? output,
String Function(String loggerName, String message)? preProcess,
bool includeStackTrace = false,
}) {
final logglyUrl = Uri.parse("https://logs-01.loggly.com/inputs/$logglyToken");

processRecord(LogRecord record) {
String message = preProcess != null ? preProcess(record.loggerName, record.message) : record.message;

Expand All @@ -30,18 +36,25 @@ void initLogging(
}
}

final tags = <String>[record.level.name, if (record.loggerName.isNotEmpty) record.loggerName];

if (tagsProvider != null) tags.addAll(tagsProvider.call(record));
final tags = <String>[
record.level.name,
...defaultTags,
if (record.loggerName.isNotEmpty) record.loggerName,
if (tagsProvider != null) ...tagsProvider.call(record),
];

// SEVERE messages will be sent to loggly anyway in
if (record.level == Level.SEVERE || (verboseLoggers?.contains(record.loggerName) == true)) {
loggly(logglyUrl, message, tags: tags);
final task = collector.collect(message, tags: tags);
tasks.add(task);
task.catchError((e, trace) {
// ignore: avoid_print
print("Error sending message to collector $e $trace");
}).whenComplete(() => tasks.remove(task));
}

if (printToConsole?.call() == true) {
// ignore: avoid_print
print('${record.loggerName} $message ${record.stackTrace ?? ''}');
if (output != null) {
output(record, '${record.loggerName} $message ${record.stackTrace ?? ''}');
}
}

Expand All @@ -50,3 +63,34 @@ void initLogging(
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen(processRecord);
}

/// Wait for all logging tasks to complete
Future<void> waitForLoggingTasks() async {
try {
await Future.wait(tasks);
} catch (_) {}
}

///
/// Watch the root [Logger] and then send messages addressed [verboseLoggers] to loggly
/// [verboseLoggers] name of loggers that will be sent to loggly verbosely - i.e. INFO messages, etc
/// [tagsProvider] tags for loggly
/// [printToConsole] print message with [debugPrint]
@Deprecated('Use initRemoteLogging instead')
void initLogging(
String logglyToken, {
List<String>? verboseLoggers,
TagsProvider? tagsProvider,
bool Function()? printToConsole,
String Function(String loggerName, String message)? preProcess,
bool includeStackTrace = false,
}) {
initRemoteLogging(
LogglyCollector(logglyToken),
verboseLoggers: verboseLoggers,
tagsProvider: tagsProvider,
output: printToConsole != null ? (_, message) => print(message) : null,
preProcess: preProcess,
includeStackTrace: includeStackTrace,
);
}
6 changes: 3 additions & 3 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
name: remote_logging
description: Sending Logger() messages to loggly.com
version: 1.1.0
description: Sending Logger() messages to various remote logging services
version: 2.0.0
homepage: https://github.com/justprodev/flutter_remote_logging.git

environment:
sdk: '>=2.18.2 <3.0.0'
sdk: '>=3.0.0 <4.0.0'

dependencies:
http:
Expand Down
Loading