@atomist/sdm-core
Version:
Atomist Software Delivery Machine - Implementation
168 lines (151 loc) • 5.78 kB
text/typescript
/*
* Copyright © 2019 Atomist, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Configuration,
defaultHttpClientFactory,
HttpClient,
HttpMethod,
logger,
} from "@atomist/automation-client";
import { redact } from "@atomist/automation-client/lib/util/redact";
import {
format,
ProgressLog,
} from "@atomist/sdm";
import * as _ from "lodash";
import os = require("os");
function* timestampGenerator(): Iterator<Date> {
while (true) {
yield new Date();
}
}
/**
* Post log to Atomist Rolar service for it to persist
*/
export class RolarProgressLog implements ProgressLog {
private readonly httpClient: HttpClient;
private localLogs: LogData[] = [];
private readonly timer: any;
private readonly rolarBaseUrl: string;
private readonly bufferSizeLimit: number;
private readonly timerInterval: number;
private readonly redact: boolean;
constructor(private readonly logPath: string[],
configuration: Configuration,
private readonly logLevel: string = "info",
private readonly timestamper: Iterator<Date> = timestampGenerator()) {
this.rolarBaseUrl = _.get(configuration, "sdm.rolar.url", "https://rolar.atomist.com");
this.bufferSizeLimit = _.get(configuration, "sdm.rolar.bufferSize", 10240);
this.timerInterval = _.get(configuration, "sdm.rolar.flushInterval", 2000);
this.redact = _.get(configuration, "redact.log", false);
if (this.timerInterval > 0) {
this.timer = setInterval(() => this.flush(), this.timerInterval).unref();
}
this.httpClient = _.get(configuration, "http.client.factory", defaultHttpClientFactory()).create(this.rolarBaseUrl);
}
get name(): string {
return this.logPath.join("/");
}
get url(): string {
return `${this.rolarBaseUrl}/logs/${this.name}`;
}
public async isAvailable(): Promise<boolean> {
const url = `${this.rolarBaseUrl}/api/logs`;
try {
await this.httpClient.exchange(url, { method: HttpMethod.Head });
return true;
} catch (e) {
logger.warn(`Rolar logger is not available at ${url}: ${e}`);
return false;
}
}
public write(msg: string = "", ...args: string[]): void {
const fmsg = format(msg, ...args);
const line = this.redact ? redact(fmsg) : fmsg;
const now: Date = this.timestamper.next().value;
this.localLogs.push({
level: this.logLevel,
message: line,
timestamp: this.constructUtcTimestamp(now),
timestampMillis: this.constructMillisTimestamp(now),
});
const bufferSize = this.localLogs.reduce((acc, logData) => acc + logData.message.length, 0);
if (bufferSize > this.bufferSizeLimit) {
// tslint:disable-next-line:no-floating-promises
this.flush();
}
}
public flush(): Promise<any> {
return this.postLogs(false);
}
public close(): Promise<any> {
if (this.timer) {
clearInterval(this.timer);
}
return this.postLogs(true);
}
private async postLogs(isClosed: boolean): Promise<any> {
const postingLogs = this.localLogs;
this.localLogs = [];
if (isClosed === true || (!!postingLogs && postingLogs.length > 0)) {
const closedRequestParam = isClosed ? "?closed=true" : "";
const url = `${this.rolarBaseUrl}/api/logs/${this.logPath.join("/")}${closedRequestParam}`;
let result;
try {
result = await this.httpClient.exchange(url, {
method: HttpMethod.Post,
body: {
host: os.hostname(),
content: postingLogs || [],
},
headers: { "Content-Type": "application/json" },
retry: {
retries: 0,
},
options: {
timeout: 2500,
},
});
} catch (err) {
this.localLogs = postingLogs.concat(this.localLogs);
if (!/timeout.*exceeded/i.test(err.message)) {
logger.error(err.message);
} else {
logger.debug("Calling rolar timed out");
}
}
return result;
}
return Promise.resolve();
}
private constructUtcTimestamp(d: Date): string {
const now: Date = d;
const date = [now.getUTCMonth() + 1, now.getUTCDate(), now.getUTCFullYear()]
.map(t => _.padStart(t.toString(), 2, "0"));
const time = [now.getUTCHours(), now.getUTCMinutes(), now.getUTCSeconds()]
.map(t => _.padStart(t.toString(), 2, "0"));
return `${date.join("/")} ${time.join(":")}.${_.padStart(now.getUTCMilliseconds().toString(), 3, "0")}`;
}
private constructMillisTimestamp(d: Date): number {
return d.valueOf();
}
}
interface LogData {
level: string;
message: string;
timestamp: string;
timestampMillis: number;
}