Add collect the logs

This commit is contained in:
Sambo Chea 2023-05-27 01:06:18 +07:00
commit 467d1c6edf
12 changed files with 774 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -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

4
CHANGELOG.md Normal file
View File

@ -0,0 +1,4 @@
## 1.0.0
- Initial version.
- Add collect the logs.

32
README.md Normal file
View File

@ -0,0 +1,32 @@
# vLogs SDK for Dart
A simple way to collect logs and send to the server via simple SDK.
- [x] Collect the logs
- [ ] Support local retries
## Usages
```dart
import 'package:vlogs/vlogs.dart';
void main() async {
final APP_ID = "xxx";
final API_KEY = "vlogs_xxx";
final sdk = VLogs.create(APP_ID, API_KEY);
var request = CollectorRequest.builder()
.message("Hello World")
.source(CollectorSource.mobile.name)
.type(CollectorType.log.name)
.build();
var response = await sdk.collect(request);
print("Response: ${response.toJson()}");
}
```
### Contributors
- Sambo Chea <sombochea@cubetiqs.com>

30
analysis_options.yaml Normal file
View File

@ -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

View File

@ -0,0 +1,19 @@
// ignore_for_file: non_constant_identifier_names
import 'package:vlogs/vlogs.dart';
void main() async {
final APP_ID = "72bd14c306a91fa8a590330e3898ddcc";
final API_KEY = "vlogs_gX9WwSdKatMNdpUClLU0IfCx575tvdoeQ";
final sdk = VLogs.create(APP_ID, API_KEY);
var request = CollectorRequest.builder()
.message("Hello World")
.source(CollectorSource.mobile.name)
.type(CollectorType.log.name)
.build();
var response = await sdk.collect(request);
print("Response: ${response.toJson()}");
}

88
lib/src/base.dart Normal file
View File

@ -0,0 +1,88 @@
// ignore_for_file: non_constant_identifier_names
import 'package:logger/logger.dart';
import 'package:vlogs/src/model.dart';
import 'package:vlogs/src/service.dart';
import 'package:vlogs/src/util.dart';
class VLogs {
static final _logger = Logger();
static final String NAME = 'vlogs';
static final String VERSION = '1.0.0';
static final String VERSION_CODE = '1';
static final String DEFAULT_VLOGS_URL = "https://vlogs-sg1.onrender.com";
static final String APP_ID_HEADER_PREFIX = "x-app-id";
static final String API_KEY_HEADER_PREFIX = "x-api-key";
static final int DEFAULT_CONNECT_TIMEOUT = 60; // seconds
late String _baseUrl;
late String _appId;
late String _apiKey;
late VLogsService _service;
VLogs(VLogsOptions options) {
_baseUrl = options.url ?? DEFAULT_VLOGS_URL;
if (options.appId == null || options.apiKey == null) {
throw Exception("AppId and ApiKey are required");
}
if (options.apiKey!.isEmpty || options.appId!.isEmpty) {
throw Exception("AppId and ApiKey are required");
}
_appId = options.appId!;
_apiKey = options.apiKey!;
_service = VLogsService(_baseUrl);
_logger.i("VLogs: Initialized AppID: $_appId | SDK Version: $VERSION-$VERSION_CODE");
}
Future<CollectorResponse> collect(CollectorRequest request) async {
var headers = {
APP_ID_HEADER_PREFIX: _appId,
API_KEY_HEADER_PREFIX: _apiKey,
"Content-Type": "application/json",
};
var hostname = Util.getSystemHostname();
var sender = Util.getSystemUsername();
var sdkInfo = SDKInfo.builder()
.hostname(hostname)
.sender(sender)
.name(VLogs.NAME)
.version(VLogs.VERSION)
.versionCode(VLogs.VERSION_CODE)
.build();
if (request.target == null) {
request.target = Target.builder().sdkInfo(sdkInfo).build();
} else {
request.target!.sdkInfo = sdkInfo;
}
request.userAgent ??= "vlogs-dart-sdk/$VERSION-$VERSION_CODE ($hostname)";
var response = await _service.post(request.toJson(), headers: headers);
return response;
}
void collectAsync(CollectorRequest request) async {
try {
var response = await collect(request);
_logger.i("VLogs: ${response.message}");
} catch (e) {
_logger.e("VLogs: ${e.toString()}");
}
}
static VLogs createWithOptions(VLogsOptions options) {
return VLogs(options);
}
static VLogs create(String appId, String apiKey) {
return createWithOptions(VLogsOptions(
appId: appId,
apiKey: apiKey,
));
}
}

487
lib/src/model.dart Normal file
View File

@ -0,0 +1,487 @@
import 'dart:convert';
import 'package:uuid/uuid.dart';
enum CollectorType { error, event, metric, trace, log, span }
enum CollectorSource { web, mobile, server, desktop, iot, other }
enum TelegramParseMode { markdown, markdownV2, html }
class Telegram {
String? token;
String? chatId;
TelegramParseMode? parseMode;
bool? disabled;
Telegram({
this.token,
this.chatId,
this.parseMode,
this.disabled,
});
Map<String, dynamic> toMap() {
return {
'token': token,
'chat_id': chatId,
'parse_mode': parseMode,
'disabled': disabled,
};
}
static TelegramBuilder builder() {
return TelegramBuilder();
}
}
class TelegramBuilder {
String? _token;
String? _chatId;
TelegramParseMode? _parseMode;
bool? _disabled;
TelegramBuilder();
TelegramBuilder token(String? token) {
_token = token;
return this;
}
TelegramBuilder chatId(String? chatId) {
_chatId = chatId;
return this;
}
TelegramBuilder parseMode(TelegramParseMode? parseMode) {
_parseMode = parseMode;
return this;
}
TelegramBuilder disabled(bool? disabled) {
_disabled = disabled;
return this;
}
Telegram build() {
return Telegram(
token: _token,
chatId: _chatId,
parseMode: _parseMode,
disabled: _disabled,
);
}
}
class Discord {
String? webhookId;
String? webhookToken;
String? webhookUrl;
bool? disabled;
Discord({
this.webhookId,
this.webhookToken,
this.webhookUrl,
this.disabled,
});
Discord._builder(DiscordBuilder builder)
: webhookId = builder._webhookId,
webhookToken = builder._webhookToken,
webhookUrl = builder._webhookUrl,
disabled = builder._disabled;
Map<String, dynamic> toMap() {
return {
'webhook_id': webhookId,
'webhook_token': webhookToken,
'webhook_url': webhookUrl,
'disabled': disabled,
};
}
static DiscordBuilder builder() {
return DiscordBuilder();
}
}
class DiscordBuilder {
String? _webhookId;
String? _webhookToken;
String? _webhookUrl;
bool? _disabled;
DiscordBuilder();
DiscordBuilder webhookId(String? webhookId) {
_webhookId = webhookId;
return this;
}
DiscordBuilder webhookToken(String? webhookToken) {
_webhookToken = webhookToken;
return this;
}
DiscordBuilder webhookUrl(String? webhookUrl) {
_webhookUrl = webhookUrl;
return this;
}
DiscordBuilder disabled(bool? disabled) {
_disabled = disabled;
return this;
}
Discord build() {
return Discord._builder(this);
}
}
class SDKInfo {
String? name;
String? version;
String? versionCode;
String? hostname;
String? sender;
SDKInfo({
this.name,
this.version,
this.versionCode,
this.hostname,
this.sender,
});
SDKInfo._builder(SDKInfoBuilder builder)
: name = builder._name,
version = builder._version,
versionCode = builder._versionCode,
hostname = builder._hostname,
sender = builder._sender;
Map<String, dynamic> toMap() {
return {
'name': name,
'version': version,
'version_code': versionCode,
'hostname': hostname,
'sender': sender,
};
}
static SDKInfoBuilder builder() {
return SDKInfoBuilder();
}
}
class SDKInfoBuilder {
String? _name;
String? _version;
String? _versionCode;
String? _hostname;
String? _sender;
SDKInfoBuilder();
SDKInfoBuilder name(String? name) {
_name = name;
return this;
}
SDKInfoBuilder version(String? version) {
_version = version;
return this;
}
SDKInfoBuilder versionCode(String? versionCode) {
_versionCode = versionCode;
return this;
}
SDKInfoBuilder hostname(String? hostname) {
_hostname = hostname;
return this;
}
SDKInfoBuilder sender(String? sender) {
_sender = sender;
return this;
}
SDKInfo build() {
return SDKInfo._builder(this);
}
}
class Target {
Telegram? telegram;
Discord? discord;
SDKInfo? sdkInfo;
Target({
this.telegram,
this.discord,
this.sdkInfo,
});
Map<String, dynamic> toMap() {
return {
'telegram': telegram?.toMap(),
'discord': discord?.toMap(),
'sdk_info': sdkInfo?.toMap(),
};
}
static TargetBuilder builder() {
return TargetBuilder();
}
}
class TargetBuilder {
Telegram? _telegram;
Discord? _discord;
SDKInfo? _sdkInfo;
TargetBuilder();
TargetBuilder telegram(Telegram? telegram) {
_telegram = telegram;
return this;
}
TargetBuilder discord(Discord? discord) {
_discord = discord;
return this;
}
TargetBuilder sdkInfo(SDKInfo? sdkInfo) {
_sdkInfo = sdkInfo;
return this;
}
Target build() {
return Target(
telegram: _telegram,
discord: _discord,
sdkInfo: _sdkInfo,
);
}
}
class CollectorRequest {
String? id;
String? type;
String? source;
String? message;
dynamic data;
String? userAgent;
int? timestamp;
Target? target;
List<String>? tags;
CollectorRequest(
{this.id,
this.type,
this.source,
this.message,
this.data,
this.userAgent,
this.timestamp,
this.target,
this.tags});
String? getId() {
id ??= Uuid().v4();
return id;
}
int? getTimestamp() {
timestamp ??= DateTime.now().millisecondsSinceEpoch;
return timestamp;
}
Map<String, dynamic> toMap() {
return {
'id': getId(),
'type': type,
'source': source,
'message': message,
'data': data,
'user_agent': userAgent,
'timestamp': getTimestamp(),
'target': target?.toMap(),
'tags': tags,
};
}
String toJson() => json.encode(toMap());
static CollectorRequestBuilder builder() {
return CollectorRequestBuilder();
}
}
class CollectorRequestBuilder {
String? _id;
String? _type;
String? _source;
String? _message;
dynamic _data;
String? _userAgent;
int? _timestamp;
Target? _target;
List<String>? _tags;
CollectorRequestBuilder();
CollectorRequestBuilder id(String? id) {
_id = id;
return this;
}
CollectorRequestBuilder type(String? type) {
_type = type;
return this;
}
CollectorRequestBuilder source(String? source) {
_source = source;
return this;
}
CollectorRequestBuilder message(String? message) {
_message = message;
return this;
}
CollectorRequestBuilder data(dynamic data) {
_data = data;
return this;
}
CollectorRequestBuilder userAgent(String? userAgent) {
_userAgent = userAgent;
return this;
}
CollectorRequestBuilder timestamp(int? timestamp) {
_timestamp = timestamp;
return this;
}
CollectorRequestBuilder target(Target? target) {
_target = target;
return this;
}
CollectorRequestBuilder tags(List<String>? tags) {
_tags = tags;
return this;
}
CollectorRequest build() {
return CollectorRequest(
id: _id,
type: _type,
source: _source,
message: _message,
data: _data,
userAgent: _userAgent,
timestamp: _timestamp,
target: _target,
tags: _tags,
);
}
}
class CollectorResponse {
String? message;
String? id;
CollectorResponse({this.message, this.id});
bool get isSuccess => message == 'ok';
Map<String, dynamic> toMap() {
return {
'id': id,
'message': message,
};
}
String toJson() => json.encode(toMap());
factory CollectorResponse.fromMap(Map<String, dynamic> map) {
return CollectorResponse(
message: map['message'],
id: map['id'],
);
}
factory CollectorResponse.fromJson(String source) =>
CollectorResponse.fromMap(json.decode(source));
}
class VLogsOptions {
String? url;
String? appId;
String? apiKey;
int? connectionTimeout;
bool? testConnection;
VLogsOptions({
this.url,
this.appId,
this.apiKey,
this.connectionTimeout,
this.testConnection,
});
VLogsOptions._builder(VLogsOptionsBuilder builder)
: url = builder._url,
appId = builder._appId,
apiKey = builder._apiKey,
connectionTimeout = builder._connectionTimeout,
testConnection = builder._testConnection;
}
class VLogsOptionsBuilder {
String? _url;
String? _appId;
String? _apiKey;
int? _connectionTimeout;
bool? _testConnection;
VLogsOptionsBuilder();
VLogsOptionsBuilder url(String? url) {
_url = url;
return this;
}
VLogsOptionsBuilder appId(String? appId) {
_appId = appId;
return this;
}
VLogsOptionsBuilder apiKey(String? apiKey) {
_apiKey = apiKey;
return this;
}
VLogsOptionsBuilder connectionTimeout(int? connectionTimeout) {
_connectionTimeout = connectionTimeout;
return this;
}
VLogsOptionsBuilder testConnection(bool? testConnection) {
_testConnection = testConnection;
return this;
}
VLogsOptions build() {
return VLogsOptions._builder(this);
}
}

32
lib/src/service.dart Normal file
View File

@ -0,0 +1,32 @@
import 'package:vlogs/src/model.dart';
import 'package:http/http.dart' as http;
class VLogsService {
late final String url;
VLogsService(String baseUrl) {
url = '$baseUrl/api/v1/collector';
}
Future<CollectorResponse> post(String body, {headers}) async {
var request = http.Request('POST', Uri.parse(url));
request.body = body;
// print("Request Body: ${request.body}");
if (headers != null) {
request.headers.addAll(headers);
}
var response = await request.send();
if (response.statusCode == 200 ||
response.statusCode == 201 ||
response.statusCode == 202) {
var json = await response.stream.bytesToString();
return CollectorResponse.fromJson(json);
} else {
throw Exception(
'Failed to post data to vlogs server with status code: ${response.statusCode}');
}
}
}

26
lib/src/util.dart Normal file
View File

@ -0,0 +1,26 @@
import 'dart:convert';
import 'dart:io';
class Util {
static String? toJSON(dynamic data) {
try {
return jsonEncode(data);
} catch (e) {
return null;
}
}
static String getSystemHostname() {
String name = Platform.localHostname;
if (name.isEmpty) {
name = 'unknown';
}
return name;
}
static String getSystemUsername() {
return Platform.environment['USER'] ?? 'unknown';
}
}

4
lib/vlogs.dart Normal file
View File

@ -0,0 +1,4 @@
library;
export 'src/base.dart';
export 'src/model.dart';

16
pubspec.yaml Normal file
View File

@ -0,0 +1,16 @@
name: vlogs
description: A simple way to collect logs and send to the server via simple SDK.
version: 1.0.0
repository: https://github.com/CUBETIQ/vlogs_sdk_dart.git
environment:
sdk: ^3.0.2
dependencies:
http:
uuid:
logger:
dev_dependencies:
lints: ^2.0.0
test: ^1.21.0

29
test/vlogs_test.dart Normal file
View File

@ -0,0 +1,29 @@
// ignore_for_file: non_constant_identifier_names
import 'package:test/test.dart';
import 'package:vlogs/src/model.dart';
import 'package:vlogs/vlogs.dart';
void main() {
final APP_ID = "72bd14c306a91fa8a590330e3898ddcc";
final API_KEY = "vlogs_gX9WwSdKatMNdpUClLU0IfCx575tvdoeQ";
group('A group of tests', () {
final sdk = VLogs.create(APP_ID, API_KEY);
setUp(() {
// Additional setup goes here.
});
test('Emit the logs to collector', () async {
var request = CollectorRequest.builder()
.message("Hello World")
.source(CollectorSource.mobile.name)
.type(CollectorType.log.name)
.build();
var response = await sdk.collect(request);
expect(response.id, request.getId());
});
});
}