@luminati-io/luminati-proxy
Version:
A configurable local proxy for brightdata.com
311 lines (286 loc) • 9.33 kB
JavaScript
// LICENSE_CODE ZON ISC
'use strict'; /*jslint node:true, esnext:true, es9: true*/
const cluster = require('cluster');
const {Writable} = require('stream');
const url = require('url');
const _ = require('lodash4');
const winston = require('winston');
const Transport = require('winston-transport');
const syslog = require('winston-syslog');
const logzio = require('winston-logzio');
const date = require('../util/date.js');
const zerr = require('../util/zerr.js');
const zurl = require('../util/url.js');
const lpm_config = require('../util/lpm_config.js');
const util_lib = require('./util.js');
const {printf, timestamp, combine, splat, json} = winston.format;
const DEFAULT_LEVEL = 'notice';
const REMOTE_OMIT_FIELDS = ['request_headers', 'request_body',
'response_headers', 'response_body', 'context', 'rules'];
const LOGZIO_DEFAULTS = {
host: 'listener.logz.io',
port: '8071',
protocol: 'https',
};
const CHLVL_REMOTE_RECREATE_TYPES = ['logzio'];
try {
require('winston-daily-rotate-file');
} catch(e){
console.log('no winston-daily-rotate-file');
}
const upper = winston.format(info=>{
info.level = info.level.toUpperCase();
return info;
});
const prepare_remote_log = winston.format(info=>{
if (info.message)
info.message = _.omit(info.message, REMOTE_OMIT_FIELDS);
return info;
});
const remote_log_format = combine(prepare_remote_log(), json());
const format = printf(opt=>{
const cat = opt.category ? opt.category+': ' : '';
const ts = date.to_sql_ms(opt.timestamp);
let pref = '';
if (cluster.isWorker)
pref = 'C'+cluster.worker.id+' ';
return `${pref}${ts} ${opt.level}: ${cat}${opt.message}`;
});
const get_console_transport = (level=DEFAULT_LEVEL)=>
new winston.transports.Console({
level,
format: combine(timestamp(), upper(), splat(), format),
// transporting to stdout will cause workers to crash with EPERM, write
// err on windows server 2012r2, this redirects lvls to stderr instead
stderrLevels: lpm_config.is_win ? Object.keys(lpm_config.log_levels) :
[],
});
const get_file_transport = (level=DEFAULT_LEVEL)=>{
if (process.env.AGENT_NUM)
return null;
let ft;
try {
ft = new winston.transports.DailyRotateFile({
level,
format: combine(timestamp(), upper(), splat(), format),
dirname: lpm_config.work_dir,
filename: 'luminati-%DATE%.log',
maxSize: '50m',
maxFiles: 2,
zippedArchive: true,
});
ft.on('new', filename=>logger.lpm_filename = filename);
} catch(e){
console.log('Could not create file_transport', zerr.e2s(e));
return null;
}
return ft;
};
const get_http_transport = (opt={})=>{
const {username, password, use_ssl} = opt;
const _url = zurl.parse(opt.url);
return new winston.transports.Http({
level: 'info',
format: remote_log_format,
host: _url.hostname,
port: _url.port,
path: _url.path,
auth: username && password ? {username, password} : null,
ssl: !!use_ssl,
});
};
// https://docs.datadoghq.com/logs/log_collection/nodejs/?tab=winston30#agentless-logging
const get_datadog_transport = (opt={})=>
get_http_transport({
url: url.format({
protocol: 'https',
host: opt.host || 'http-intake.logs.datadoghq.com',
port: opt.port || null,
pathname: '/api/v2/logs',
query: {
'dd-api-key': opt.token,
ddsource: opt.source,
ddtags: opt.tags,
environment: 'prod'
}
}),
use_ssl: true
});
const get_logzio_transport = (opt={})=>
new logzio({
level: 'info',
format: remote_log_format,
token: opt.token,
type: opt.source,
host: opt.host || LOGZIO_DEFAULTS.host,
port: opt.port || LOGZIO_DEFAULTS.port,
protocol: LOGZIO_DEFAULTS.protocol,
debug: current_level == lpm_config.log_levels.debug,
callback: err=>{
if (err)
logger.warn('Logzio delivery error: %s', err);
},
});
const get_s3_transport = (opt={})=>{
util_lib.s3.prepare_opt(opt);
let {bucket, instant, compress, rotation_hour, encryption} = opt;
return new winston.transports.Stream({
level: 'info',
format: remote_log_format,
stream: new s3_stream({
config: util_lib.s3.get_config(opt),
max_file_size: 20000000, // 20MB
buffer_size: 200000, // 200KB
region: 'us-east-1',
bucket: bucket,
folder: util_lib.s3.get_target(opt),
upload_every: instant ? 1 : date.ms.MIN,
rotate_every: rotation_hour ? date.ms.HOUR : 10*date.ms.MIN,
compress: !!compress,
storage_class: 'STANDARD_IA',
name_format: util_lib.s3.get_name_format(opt),
tags: util_lib.s3.get_tags(opt),
server_side_encryption: !!encryption,
})
});
};
let current_level = DEFAULT_LEVEL;
let remote_settings = {
type: null,
opt: {},
};
let console_transport = get_console_transport(current_level);
let file_transport = get_file_transport(current_level);
winston.loggers.add('default', {
levels: lpm_config.log_levels,
transports: [
console_transport,
file_transport,
].filter(Boolean),
});
const logger = winston.loggers.get('default');
logger.set_level = level=>{
if (level==current_level)
return;
current_level = level;
logger.remove(console_transport);
console_transport = get_console_transport(current_level);
logger.add(console_transport);
if (!process.env.AGENT_NUM)
{
logger.remove(file_transport);
file_transport = get_file_transport(current_level);
logger.add(file_transport);
}
logger.level = level;
if (CHLVL_REMOTE_RECREATE_TYPES.includes(remote_settings.type))
logger.create_remote(remote_settings.opt);
};
logger.create_remote = (opt={})=>{
let type = opt.type;
let new_transport = logger.remote_transports[type];
if (!type || !new_transport)
return null;
new_transport.validate(opt);
logger.debug('Creating remote logger %s', type);
if (remote_settings.type)
{
let old_transport = logger.remote_transports[remote_settings.type];
remote_logger.remove(old_transport.instance);
}
new_transport.instance = new_transport.create_fn(opt);
new_transport.instance.on('warn', e=>logger.warn('Logs delivery error: %s',
e.message));
new_transport.instance.on('logged', r=>
logger.debug('Logs %s delivered: %s', type, r));
remote_logger.add(new_transport.instance);
remote_settings = {type, opt};
return remote_logger;
};
const api_logger = logger.child({category: 'API'});
logger.get_api_mw = port=>(req, res, next)=>{
api_logger.debug('[%s] %s %s', port, req.method, req.originalUrl);
const start_time = Date.now();
res.on('finish', ()=>{
const elapsed_time = Date.now()-start_time;
api_logger.info('[%s] %fms %s %s %s %s', port, elapsed_time,
req.method, req.originalUrl, res.statusCode, res.statusMessage);
});
next();
};
module.exports = logger;
if (!winston.transports.Syslog)
winston.transports.Syslog = syslog.Syslog;
const syslog_transport = new winston.transports.Syslog({
level: 'info',
app_name: 'lpm',
localhost: null,
protocol: 'unix',
path: '/dev/log',
});
class Null_transport extends Transport {
get name(){
return 'null transport';
}
log(info, callback){
callback(null, true);
}
}
const null_transport = new Null_transport();
winston.loggers.add('reqs', {
levels: lpm_config.log_levels,
transports: [
!process.env.CLOUD_LOG && null_transport,
process.env.CLOUD_LOG && syslog_transport,
].filter(Boolean),
});
logger.remote_transports = {
custom: {
instance: new Null_transport(),
create_fn: get_http_transport,
test_opt: {},
validate: util_lib.noop,
},
datadog: {
instance: new Null_transport(),
create_fn: get_datadog_transport,
test_opt: {},
validate: util_lib.noop,
},
logzio: {
instance: new Null_transport(),
create_fn: get_logzio_transport,
test_opt: {},
validate: util_lib.noop,
},
s3: {
instance: new Null_transport(),
create_fn: get_s3_transport,
test_opt: {instant: true},
validate: util_lib.s3.validate,
}
};
let s3_stream;
if (process.env.AGENT_NUM)
{
try {
s3_stream = require('s3-streamlogger').S3StreamLogger;
} catch(e){
s3_stream = Writable;
console.log('no s3-streamlogger');
}
}
else
{
s3_stream = Writable;
}
winston.loggers.add('remote', {
levels: lpm_config.log_levels,
transports: Object.values(logger.remote_transports)
.map(t=>t.instance).filter(Boolean),
});
const remote_logger = winston.loggers.get('remote');
winston.loggers.add('test', {
levels: lpm_config.log_levels,
transports: [null_transport],
});