Skip to content

Commit

Permalink
init central cloudwatch
Browse files Browse the repository at this point in the history
  • Loading branch information
Ashwin Kumar committed Dec 5, 2023
1 parent 3340588 commit ac3c11e
Show file tree
Hide file tree
Showing 16 changed files with 410 additions and 35 deletions.
12 changes: 12 additions & 0 deletions packages/aws-amplify/src/initSingleton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
LibraryOptions,
ResourcesConfig,
defaultStorage,
ConsoleProvider,
} from '@aws-amplify/core';
import {
LegacyConfig,
Expand All @@ -23,6 +24,17 @@ export const DefaultAmplify = {
) {
let resolvedResourceConfig: ResourcesConfig;

// add console logger provider by default
if (!Amplify.libraryOptions.Logger) {
libraryOptions = {
...libraryOptions,
Logger: {
...libraryOptions?.Logger,
Console: { provider: ConsoleProvider },
},
};
}

if (Object.keys(resourceConfig).some(key => key.startsWith('aws_'))) {
resolvedResourceConfig = parseAWSExports(resourceConfig);
} else {
Expand Down
2 changes: 2 additions & 0 deletions packages/aws-amplify/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ export {
sessionStorage,
sharedInMemoryStorage,
KeyValueStorageInterface,
generateLogger,
CloudWatchProvider,
} from '@aws-amplify/core';
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
],
"dependencies": {
"@aws-crypto/sha256-js": "5.2.0",
"@aws-sdk/client-cloudwatch-logs": "3.398.0",
"@aws-sdk/types": "3.398.0",
"@smithy/util-hex-encoding": "2.0.0",
"@types/uuid": "^9.0.0",
Expand Down
36 changes: 36 additions & 0 deletions packages/core/src/Logger/AdministrateLogger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

// PENDING: logger configs to be taken from singleton
// PENDING: log filtering
import { Amplify } from '../singleton/Amplify';
import { LogCallInputs, LogLevel } from './types';

const logLevelIndex = ['VERBOSE', 'DEBUG', 'INFO', 'WARN', 'ERROR'];
let logLevel: LogLevel = 'INFO';

export const log = (...params: LogCallInputs) => {
const loggers = Amplify.libraryOptions.Logger;
if (loggers)
Object.values(loggers).forEach(logger => logger.provider.log(...params));
};

export const setLogLevel = (level: LogLevel) => {
logLevel = level;
};

export const getLogLevel = (): LogLevel => {
if (typeof (<any>window) !== 'undefined' && (<any>window).LOG_LEVEL) {
const windowLog = (<any>window).LOG_LEVEL;
if (logLevelIndex.includes(windowLog)) return windowLog;
}
return logLevel;
};

export const checkLogLevel = (
level: LogLevel,
setLevel: LogLevel | undefined = undefined
): boolean => {
const targetLevel = setLevel ?? getLogLevel();
return logLevelIndex.indexOf(level) >= logLevelIndex.indexOf(targetLevel);
};
36 changes: 35 additions & 1 deletion packages/core/src/Logger/ConsoleLogger.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,42 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { InputLogEvent, Logger, LoggingProvider, LogType } from './types';
import { AWS_CLOUDWATCH_CATEGORY } from '../constants';

// TODO: cleanup these legacy logger types
// legacy logger types
enum LogType {
DEBUG = 'DEBUG',
ERROR = 'ERROR',
INFO = 'INFO',
WARN = 'WARN',
VERBOSE = 'VERBOSE',
}
interface LoggingProvider {
// return the name of you provider
getProviderName(): string;

// return the name of you category
getCategoryName(): string;

// configure the plugin
configure(config?: object): object;

// take logs and push to provider
pushLogs(logs: InputLogEvent[]): void;
}
interface InputLogEvent {
timestamp: number | undefined;
message: string | undefined;
}
interface Logger {
debug(msg: string): void;
info(msg: string): void;
warn(msg: string): void;
error(msg: string): void;
addPluggable(pluggable: LoggingProvider): void;
}

const LOG_LEVELS: Record<string, number> = {
VERBOSE: 1,
DEBUG: 2,
Expand All @@ -15,6 +48,7 @@ const LOG_LEVELS: Record<string, number> = {
/**
* Write logs
* @class Logger
* @deprecated The ConsoleLogger is deprecated. Please migrate to the `logger` function.
*/
export class ConsoleLogger implements Logger {
name: string;
Expand Down
5 changes: 4 additions & 1 deletion packages/core/src/Logger/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

export { ConsoleLogger } from './ConsoleLogger';
export { ConsoleLogger } from './ConsoleLogger'; // legacy
export { generateExternalLogger, generateInternalLogger } from './logger';
export { logger as CloudWatchProvider } from './providers/CloudWatchProvider';
export { logger as ConsoleProvider } from './providers/ConsoleProvider';
83 changes: 83 additions & 0 deletions packages/core/src/Logger/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { LogLevel } from './types';
import { log as centralizedLog } from './AdministrateLogger';
import { AmplifyLoggingCategories } from '../types';

/**
* Write logs
* @class Logger
**/
class Logger {
namespaces: string[];

constructor(namespaces: string[]) {
this.namespaces = namespaces;
}

/**
* Write INFO log
* @method
* @memeberof Logger
* @param {string|object} msg - Logging message or object
*/
info(message: string, ...objects: any) {
this._log('INFO', message, objects);
}

/**
* Write WARN log
* @method
* @memeberof Logger
* @param {string|object} msg - Logging message or object
*/
warn(message: string, ...objects: any) {
this._log('WARN', message, objects);
}

/**
* Write ERROR log
* @method
* @memeberof Logger
* @param {string|object} msg - Logging message or object
*/
error(message: string, ...objects: any) {
this._log('ERROR', message, objects);
}

/**
* Write DEBUG log
* @method
* @memeberof Logger
* @param {string|object} msg - Logging message or object
*/
debug(message: string, ...objects: any) {
this._log('DEBUG', message, objects);
}

/**
* Write VERBOSE log
* @method
* @memeberof Logger
* @param {string|object} msg - Logging message or object
*/
verbose(message: string, ...objects: any) {
this._log('VERBOSE', message, objects);
}

_log(logLevel: LogLevel, message: string, ...objects: any) {
centralizedLog(this.namespaces, logLevel, message, objects);
}
}

export const generateInternalLogger = (
forCategory: AmplifyLoggingCategories,
...forNamespace: string[]
) => {
return new Logger([forCategory, ...forNamespace]);
};

export const generateExternalLogger = (...forNamespace: string[]) => {
return new Logger(forNamespace);
};
80 changes: 80 additions & 0 deletions packages/core/src/Logger/providers/CloudWatchProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

// PENDING: complete implementation of CloudWatchProvider
import { Amplify } from '../../singleton/Amplify';
import { fetchAuthSession } from '../../index';
import {
CloudWatchLogsClient,
PutLogEventsCommand,
} from '@aws-sdk/client-cloudwatch-logs';
import { checkLogLevel } from '../AdministrateLogger';
import { LogLevel, Logger, isCloudWatchOptions } from '../types';

export const logger: Logger = {
log: (
namespaces: string[],
logLevel: LogLevel,
message: string,
objects: object[]
) => {
const timestamp = getTimestamp();
const prefix = `[${logLevel}] ${timestamp} ${namespaces.join(' - ')}`;

// todo: add timestamp, object
if (checkLogLevel(logLevel))
putLogEvents({ message: `${prefix} ${message}` });
},
};

async function putLogEvents({
message,
timestamp,
}: {
message: string;
timestamp?: number;
}) {
try {
// TODO assert errors for required items
const config = Amplify.libraryOptions?.Logger?.CloudWatch;

// add assert instead
if (!config || !isCloudWatchOptions(config)) return;
const { logGroupName, logStreamName, region } = config;

let session;
try {
session = await fetchAuthSession();
} catch (error) {
return Promise.reject('No credentials');
}
const client = new CloudWatchLogsClient({
region,
credentials: session.credentials,
});

const timestamp = new Date().getTime();
const params = {
logEvents: [{ message, timestamp }],
logGroupName,
logStreamName,
};

await client.send(new PutLogEventsCommand(params));
} catch (error) {
console.error('Error putting log events:', error);
}
}

const getTimestamp = () => {
const dt = new Date();
return (
[padding(dt.getMinutes()), padding(dt.getSeconds())].join(':') +
'.' +
dt.getMilliseconds()
);
};

const padding = (n: number) => {
return n < 10 ? '0' + n : '' + n;
};
54 changes: 54 additions & 0 deletions packages/core/src/Logger/providers/ConsoleProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

// PENDING: complete implementation of ConsoleProvider
import { checkLogLevel } from '../AdministrateLogger';
import { LogLevel, Logger } from '../types';
export const logger: Logger = {
log: (
namespaces: string[],
logLevel: LogLevel,
message: string,
objects: object[]
) => {
const logFcn = getConsoleLogFcn(logLevel);
const prefix = `[${logLevel}] ${timestamp()} ${namespaces.join(' - ')}`;
if (checkLogLevel(logLevel)) logFcn(prefix, message, ...objects);
},
};

const getConsoleLogFcn = (logLevel: LogLevel) => {
let fcn = console.log.bind(console);
switch (logLevel) {
case 'DEBUG': {
fcn = console.debug?.bind(console) ?? fcn;
break;
}
case 'ERROR': {
fcn = console.error?.bind(console) ?? fcn;
break;
}
case 'INFO': {
fcn = console.info?.bind(console) ?? fcn;
break;
}
case 'WARN': {
fcn = console.warn?.bind(console) ?? fcn;
break;
}
}
return fcn;
};

const padding = (n: number) => {
return n < 10 ? '0' + n : '' + n;
};

const timestamp = () => {
const dt = new Date();
return (
[padding(dt.getMinutes()), padding(dt.getSeconds())].join(':') +
'.' +
dt.getMilliseconds()
);
};
Loading

0 comments on commit ac3c11e

Please sign in to comment.