Add vlogs sdk for ts

This commit is contained in:
2023-05-27 11:16:06 +07:00
commit 430d052865
12 changed files with 4695 additions and 0 deletions

2
src/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export * from './model'
export * from './vlgos'

692
src/model.ts Normal file
View File

@@ -0,0 +1,692 @@
import { generateUUID } from "./util";
enum CollectorType {
Error,
Event,
Metric,
Trace,
Log,
Span,
}
enum CollectorSource {
Web,
Mobile,
Server,
Desktop,
IoT,
Other,
}
enum TelegramParseMode {
Markdown,
MarkdownV2,
HTML,
}
interface TelegramOptions {
token?: string;
chatId?: string;
parseMode?: TelegramParseMode;
disabled?: boolean;
extras?: any;
}
class Telegram {
token?: string;
chatId?: string;
parseMode?: TelegramParseMode;
disabled?: boolean;
extras?: any;
constructor(options: TelegramOptions = {}) {
this.token = options.token;
this.chatId = options.chatId;
this.parseMode = options.parseMode;
this.disabled = options.disabled;
this.extras = options.extras;
}
toMap(): Record<string, any> {
return {
'token': this.token,
'chat_id': this.chatId,
'parse_mode': this.parseMode,
'disabled': this.disabled,
'extras': this.extras,
};
}
static builder(): TelegramBuilder {
return new TelegramBuilder();
}
}
class TelegramBuilder {
private _token?: string;
private _chatId?: string;
private _parseMode?: TelegramParseMode;
private _disabled?: boolean;
private _extras?: any;
constructor() { }
token(token?: string): TelegramBuilder {
this._token = token;
return this;
}
chatId(chatId?: string): TelegramBuilder {
this._chatId = chatId;
return this;
}
parseMode(parseMode?: TelegramParseMode): TelegramBuilder {
this._parseMode = parseMode;
return this;
}
disabled(disabled?: boolean): TelegramBuilder {
this._disabled = disabled;
return this;
}
extras(extras?: any): TelegramBuilder {
this._extras = extras;
return this;
}
build(): Telegram {
return new Telegram({
token: this._token,
chatId: this._chatId,
parseMode: this._parseMode,
disabled: this._disabled,
extras: this._extras,
});
}
}
class Discord {
webhookId?: string;
webhookToken?: string;
webhookUrl?: string;
disabled?: boolean;
extras?: any;
constructor({
webhookId,
webhookToken,
webhookUrl,
disabled,
extras,
}: {
webhookId?: string;
webhookToken?: string;
webhookUrl?: string;
disabled?: boolean;
extras?: any;
}) {
this.webhookId = webhookId;
this.webhookToken = webhookToken;
this.webhookUrl = webhookUrl;
this.disabled = disabled;
this.extras = extras;
}
toMap(): Record<string, any> {
return {
webhook_id: this.webhookId,
webhook_token: this.webhookToken,
webhook_url: this.webhookUrl,
disabled: this.disabled,
extras: this.extras,
};
}
static builder(): DiscordBuilder {
return new DiscordBuilder();
}
}
class DiscordBuilder {
private _webhookId?: string;
private _webhookToken?: string;
private _webhookUrl?: string;
private _disabled?: boolean;
private _extras?: any;
constructor() { }
webhookId(webhookId: string | undefined): DiscordBuilder {
this._webhookId = webhookId;
return this;
}
webhookToken(webhookToken: string | undefined): DiscordBuilder {
this._webhookToken = webhookToken;
return this;
}
webhookUrl(webhookUrl: string | undefined): DiscordBuilder {
this._webhookUrl = webhookUrl;
return this;
}
disabled(disabled: boolean | undefined): DiscordBuilder {
this._disabled = disabled;
return this;
}
extras(extras: any): DiscordBuilder {
this._extras = extras;
return this;
}
build(): Discord {
return new Discord({
webhookId: this._webhookId,
webhookToken: this._webhookToken,
webhookUrl: this._webhookUrl,
disabled: this._disabled,
extras: this._extras,
});
}
}
class SDKInfo {
name?: string;
version?: string;
versionCode?: string;
hostname?: string;
sender?: string;
constructor({
name,
version,
versionCode,
hostname,
sender,
}: {
name?: string;
version?: string;
versionCode?: string;
hostname?: string;
sender?: string;
}) {
this.name = name;
this.version = version;
this.versionCode = versionCode;
this.hostname = hostname;
this.sender = sender;
}
toMap(): Record<string, any> {
return {
name: this.name,
version: this.version,
version_code: this.versionCode,
hostname: this.hostname,
sender: this.sender,
};
}
static builder(): SDKInfoBuilder {
return new SDKInfoBuilder();
}
}
class SDKInfoBuilder {
private _name?: string;
private _version?: string;
private _versionCode?: string;
private _hostname?: string;
private _sender?: string;
constructor() { }
name(name: string | undefined): SDKInfoBuilder {
this._name = name;
return this;
}
version(version: string | undefined): SDKInfoBuilder {
this._version = version;
return this;
}
versionCode(versionCode: string | undefined): SDKInfoBuilder {
this._versionCode = versionCode;
return this;
}
hostname(hostname: string | undefined): SDKInfoBuilder {
this._hostname = hostname;
return this;
}
sender(sender: string | undefined): SDKInfoBuilder {
this._sender = sender;
return this;
}
build(): SDKInfo {
return new SDKInfo({
name: this._name,
version: this._version,
versionCode: this._versionCode,
hostname: this._hostname,
sender: this._sender,
});
}
}
class Target {
telegram?: Telegram;
discord?: Discord;
sdkInfo?: SDKInfo;
constructor({
telegram,
discord,
sdkInfo,
}: {
telegram?: Telegram;
discord?: Discord;
sdkInfo?: SDKInfo;
}) {
this.telegram = telegram;
this.discord = discord;
this.sdkInfo = sdkInfo;
}
toMap(): Record<string, any> {
return {
telegram: this.telegram?.toMap(),
discord: this.discord?.toMap(),
sdk_info: this.sdkInfo?.toMap(),
};
}
merge(defaultTarget?: Target): void {
if (!defaultTarget) return;
this.telegram ||= defaultTarget.telegram;
this.discord ||= defaultTarget.discord;
}
static withTelegram(
chatId: string,
{
token,
parseMode,
disabled,
extras,
}: {
token?: string;
parseMode?: TelegramParseMode;
disabled?: boolean;
extras?: any;
} = {}
): Target {
return new Target({
telegram: new Telegram({
chatId,
token,
parseMode,
disabled,
extras,
}),
});
}
static withDiscord(
webhookUrl: string,
{
webhookId,
webhookToken,
disabled,
extras,
}: {
webhookId?: string;
webhookToken?: string;
disabled?: boolean;
extras?: any;
}
): Target {
return new Target({
discord: new Discord({
webhookUrl,
webhookId,
webhookToken,
disabled,
extras,
}),
});
}
static builder(): TargetBuilder {
return new TargetBuilder();
}
}
class TargetBuilder {
private _telegram?: Telegram;
private _discord?: Discord;
private _sdkInfo?: SDKInfo;
constructor() { }
telegram(telegram?: Telegram): TargetBuilder {
this._telegram = telegram;
return this;
}
discord(discord?: Discord): TargetBuilder {
this._discord = discord;
return this;
}
sdkInfo(sdkInfo?: SDKInfo): TargetBuilder {
this._sdkInfo = sdkInfo;
return this;
}
build(): Target {
return new Target({
telegram: this._telegram,
discord: this._discord,
sdkInfo: this._sdkInfo,
});
}
}
class Collector {
id?: string;
type?: string;
source?: string;
message?: string;
data?: any;
userAgent?: string;
timestamp?: number;
target?: Target;
tags?: string[];
constructor({
id,
type,
source,
message,
data,
userAgent,
timestamp,
target,
tags,
}: {
id?: string;
type?: string;
source?: string;
message?: string;
data?: any;
userAgent?: string;
timestamp?: number;
target?: Target;
tags?: string[];
}) {
this.id = id;
this.type = type;
this.source = source;
this.message = message;
this.data = data;
this.userAgent = userAgent;
this.timestamp = timestamp;
this.target = target;
this.tags = tags;
}
getId(): string | undefined {
if (!this.id) {
this.id = generateUUID();
}
return this.id;
}
getTimestamp(): number | undefined {
if (!this.timestamp) {
this.timestamp = Date.now();
}
return this.timestamp;
}
toMap(): Record<string, any> {
return {
id: this.getId(),
type: this.type,
source: this.source,
message: this.message,
data: this.data,
user_agent: this.userAgent,
timestamp: this.getTimestamp(),
target: this.target?.toMap(),
tags: this.tags,
};
}
toJson(): string {
return JSON.stringify(this.toMap());
}
static builder(): CollectorBuilder {
return new CollectorBuilder();
}
}
class CollectorBuilder {
private _id?: string;
private _type?: string;
private _source?: string;
private _message?: string;
private _data?: any;
private _userAgent?: string;
private _timestamp?: number;
private _target?: Target;
private _tags?: string[];
constructor() { }
id(id: string): CollectorBuilder {
this._id = id;
return this;
}
type(type: string | CollectorType): CollectorBuilder {
this._type = type?.toString();
return this;
}
source(source: string | CollectorSource): CollectorBuilder {
this._source = source?.toString();
return this;
}
message(message: string): CollectorBuilder {
this._message = message;
return this;
}
data(data: any): CollectorBuilder {
this._data = data;
return this;
}
userAgent(userAgent: string): CollectorBuilder {
this._userAgent = userAgent;
return this;
}
timestamp(timestamp: number): CollectorBuilder {
this._timestamp = timestamp;
return this;
}
target(target: Target): CollectorBuilder {
this._target = target;
return this;
}
tags(tags: string[]): CollectorBuilder {
this._tags = tags;
return this;
}
build(): Collector {
return new Collector({
id: this._id,
type: this._type,
source: this._source,
message: this._message,
data: this._data,
userAgent: this._userAgent,
timestamp: this._timestamp,
target: this._target,
tags: this._tags,
});
}
}
class CollectorResponse {
message?: string;
id?: string;
constructor({ message, id }: { message?: string; id?: string }) {
this.message = message;
this.id = id;
}
}
class VLogsOptions {
url?: string;
appId?: string;
apiKey?: string;
connectionTimeout?: number;
testConnection?: boolean;
target?: Target;
constructor({
url,
appId,
apiKey,
connectionTimeout,
testConnection,
target,
}: {
url?: string;
appId?: string;
apiKey?: string;
connectionTimeout?: number;
testConnection?: boolean;
target?: Target;
}) {
this.url = url;
this.appId = appId;
this.apiKey = apiKey;
this.connectionTimeout = connectionTimeout;
this.testConnection = testConnection;
this.target = target;
}
static builder(): VLogsOptionsBuilder {
return new VLogsOptionsBuilder();
}
}
class VLogsOptionsBuilder {
private _url?: string;
private _appId?: string;
private _apiKey?: string;
private _connectionTimeout?: number;
private _testConnection?: boolean;
private _target?: Target;
constructor() { }
url(url: string): VLogsOptionsBuilder {
this._url = url;
return this;
}
appId(appId: string): VLogsOptionsBuilder {
this._appId = appId;
return this;
}
apiKey(apiKey: string): VLogsOptionsBuilder {
this._apiKey = apiKey;
return this;
}
connectionTimeout(connectionTimeout: number): VLogsOptionsBuilder {
this._connectionTimeout = connectionTimeout;
return this;
}
testConnection(testConnection: boolean): VLogsOptionsBuilder {
this._testConnection = testConnection;
return this;
}
target(target: Target): VLogsOptionsBuilder {
this._target = target;
return this;
}
telegram(telegram: Telegram): VLogsOptionsBuilder {
if (!this._target) {
this._target = Target.builder().telegram(telegram).build();
} else {
this._target.telegram = telegram;
}
return this;
}
discord(discord: Discord): VLogsOptionsBuilder {
if (!this._target) {
this._target = Target.builder().discord(discord).build();
} else {
this._target.discord = discord;
}
return this;
}
build(): VLogsOptions {
return new VLogsOptions({
url: this._url,
appId: this._appId,
apiKey: this._apiKey,
connectionTimeout: this._connectionTimeout,
testConnection: this._testConnection,
target: this._target,
});
}
}
export {
Collector,
// CollectorBuilder,
CollectorResponse,
Target,
// TargetBuilder,
Telegram,
// TelegramBuilder,
Discord,
// DiscordBuilder,
SDKInfo,
// SDKInfoBuilder,
VLogsOptions,
// VLogsOptionsBuilder,
CollectorSource,
CollectorType,
TelegramParseMode,
}

37
src/service.ts Normal file
View File

@@ -0,0 +1,37 @@
import { Collector, CollectorResponse } from './model';
import axios, { AxiosRequestConfig } from 'axios';
class VLogsService {
private url: string;
constructor(baseUrl: string) {
this.url = `${baseUrl}/api/v1/collector`;
}
async post(body: any, headers?: any, timeout?: number): Promise<CollectorResponse> {
const config: AxiosRequestConfig = {
method: 'POST',
url: this.url,
data: body,
headers: headers,
timeout: timeout ? timeout * 1000 : undefined,
};
const response = await axios(config);
if (
response.status === 200 ||
response.status === 201 ||
response.status === 202
) {
return await response.data;
} else {
throw new Error(
`Failed to post data to vlogs server with status code: ${response.status} and message: ${response.statusText}`
);
}
}
}
export { VLogsService };

35
src/util.ts Normal file
View File

@@ -0,0 +1,35 @@
import { v4 as uuidv4 } from 'uuid';
export const getSystemHostname = () => {
let name = 'localhost';
if (typeof window !== 'undefined') {
name = window.location.hostname;
}
// @ts-ignore
if (typeof process !== 'undefined') {
// @ts-ignore
name = process.env.HOSTNAME;
}
return name;
}
export const getSystemUsername = () => {
let name = 'unknown';
if (typeof window !== 'undefined') {
name = window.navigator.userAgent;
}
// @ts-ignore
if (typeof process !== 'undefined') {
// @ts-ignore
name = process.env.USER;
}
return name;
}
export const generateUUID = () => {
return uuidv4();
}

86
src/vlgos.ts Normal file
View File

@@ -0,0 +1,86 @@
import { Collector, CollectorResponse, SDKInfo, Target, VLogsOptions } from "./model";
import { VLogsService } from "./service";
import { getSystemHostname, getSystemUsername } from "./util";
export class VLogs {
private static readonly _logger = console;
private static readonly NAME = 'vlogs';
private static readonly VERSION = '1.0.0';
private static readonly VERSION_CODE = '1';
private static readonly DEFAULT_VLOGS_URL = 'https://vlogs-sg1.onrender.com';
private static readonly APP_ID_HEADER_PREFIX = 'x-app-id';
private static readonly API_KEY_HEADER_PREFIX = 'x-api-key';
private static readonly DEFAULT_CONNECT_TIMEOUT = 60; // seconds
private _options!: VLogsOptions;
private _service!: VLogsService;
constructor(options: VLogsOptions) {
if (!options.appId || !options.apiKey) {
throw new Error('AppID and ApiKey are required');
}
// Set default options
this._options = options;
this._options.url ??= VLogs.DEFAULT_VLOGS_URL;
// Initialize service
this._service = new VLogsService(this._options.url);
VLogs._logger.log(`VLogs: Initialized AppID: ${this._options.appId} | SDK Version: ${VLogs.VERSION}-${VLogs.VERSION_CODE}`);
}
async collect(request: Collector): Promise<CollectorResponse> {
VLogs._logger.info(`VLogs: Collecting logs for ${request.getId()}`);
const headers: Record<string, string> = {
[VLogs.APP_ID_HEADER_PREFIX]: this._options.appId!,
[VLogs.API_KEY_HEADER_PREFIX]: this._options.apiKey!,
'Content-Type': 'application/json',
};
const hostname = getSystemHostname();
const sender = getSystemUsername();
const sdkInfo = SDKInfo.builder()
.hostname(hostname)
.sender(sender)
.name(VLogs.NAME)
.version(VLogs.VERSION)
.versionCode(VLogs.VERSION_CODE)
.build();
if (!request.target) {
if (this._options.target) {
request.target = this._options.target;
} else {
request.target = Target.builder().build();
}
} else {
if (this._options.target) {
request.target!.merge(this._options.target);
}
}
// Set SDK info to request
request.target!.sdkInfo = sdkInfo;
// Append user agent to request
request.userAgent ??= `vlogs-ts-sdk/${VLogs.VERSION}-${VLogs.VERSION_CODE} (${hostname})`;
const response = await this._service.post(request.toMap(), headers, this._options.connectionTimeout ?? VLogs.DEFAULT_CONNECT_TIMEOUT);
return response;
}
static create(options: VLogsOptions): VLogs {
return new VLogs(options);
}
static createWith(appId: string, apiKey: string): VLogs {
return VLogs.create(
VLogsOptions.builder()
.apiKey(apiKey)
.appId(appId)
.build()
);
}
}