diff --git a/src/currencyservice/client.js b/src/currencyservice/client.js index 2d367e0..f381dc8 100644 --- a/src/currencyservice/client.js +++ b/src/currencyservice/client.js @@ -20,6 +20,7 @@ require('@google-cloud/trace-agent').start(); const path = require('path'); const grpc = require('grpc'); const leftPad = require('left-pad'); +const pino = require('pino'); const PROTO_PATH = path.join(__dirname, './proto/demo.proto'); const PORT = 7000; @@ -28,6 +29,13 @@ const shopProto = grpc.load(PROTO_PATH).hipstershop; const client = new shopProto.CurrencyService(`localhost:${PORT}`, grpc.credentials.createInsecure()); +const logger = pino({ + name: 'currencyservice-client', + messageKey: 'message', + changeLevelName: 'severity', + useLevelLabels: true +}); + const request = { from: { currency_code: 'CHF', @@ -43,16 +51,16 @@ function _moneyToString (m) { client.getSupportedCurrencies({}, (err, response) => { if (err) { - console.error(`Error in getSupportedCurrencies: ${err}`); + logger.error(`Error in getSupportedCurrencies: ${err}`); } else { - console.log(`Currency codes: ${response.currency_codes}`); + logger.info(`Currency codes: ${response.currency_codes}`); } }); client.convert(request, (err, response) => { if (err) { - console.error(`Error in convert: ${err}`); + logger.error(`Error in convert: ${err}`); } else { - console.log(`Convert: ${_moneyToString(request.from)} to ${_moneyToString(response)}`); + logger.log(`Convert: ${_moneyToString(request.from)} to ${_moneyToString(response)}`); } }); diff --git a/src/currencyservice/package-lock.json b/src/currencyservice/package-lock.json index fb39603..3279481 100644 --- a/src/currencyservice/package-lock.json +++ b/src/currencyservice/package-lock.json @@ -663,16 +663,36 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" }, + "fast-json-parse": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-json-parse/-/fast-json-parse-1.0.3.tgz", + "integrity": "sha512-FRWsaZRWEJ1ESVNbDWmsAlqDk96gPQezzLghafp5J4GUKjbCz3OkAHuZs5TuPEtkbVQERysLp9xv6c24fBm8Aw==" + }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, + "fast-redact": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-1.2.0.tgz", + "integrity": "sha512-k/uSk9PtFmvYx0m7bRk5B2gZChQk4euWhrn7Mf3vYSmwZBLh7cGNuMuc/vhH1MKMPyVJMMtl9oMwPnwlKqs7CQ==" + }, + "fast-safe-stringify": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.6.tgz", + "integrity": "sha512-q8BZ89jjc+mz08rSxROs8VsrBBcn1SIw1kq9NjolL509tkABRk9io01RAjSaEv1Xb2uFLt8VtRiZbGp5H8iDtg==" + }, "findit2": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/findit2/-/findit2-2.2.3.tgz", "integrity": "sha1-WKRmaX34piBc39vzlVNri9d3pfY=" }, + "flatstr": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.8.tgz", + "integrity": "sha512-YXblbv/vc1zuVVUtnKl1hPqqk7TalZCppnKE7Pr8FI/Rp48vzckS/4SJ4Y9O9RNiI82Vcw/FydmtqdQOg1Dpqw==" + }, "follow-redirects": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.1.tgz", @@ -1570,6 +1590,26 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" }, + "pino": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/pino/-/pino-5.6.2.tgz", + "integrity": "sha512-JVMYqJkE58b2u5+t85zJLbUDOhoWDjEQqrRY1eQzuR4Ub9RIyUSQJms4deT8Joy+C/QIdrrie7NffgCm+ao9xw==", + "requires": { + "fast-json-parse": "^1.0.3", + "fast-redact": "^1.2.0", + "fast-safe-stringify": "^2.0.6", + "flatstr": "^1.0.5", + "pino-std-serializers": "^2.2.1", + "pump": "^3.0.0", + "quick-format-unescaped": "^3.0.0", + "sonic-boom": "^0.6.1" + } + }, + "pino-std-serializers": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-2.2.1.tgz", + "integrity": "sha512-QqL7kkF7eMCpFG4hpZD8UPQga/kxkkh3E62HzMzTIL4OQyijyisAnBL8msBEAml8xcb/ioGhH7UUzGxuHqczhQ==" + }, "pretty-ms": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-3.2.0.tgz", @@ -1599,6 +1639,15 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", @@ -1609,6 +1658,11 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, + "quick-format-unescaped": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-3.0.0.tgz", + "integrity": "sha512-XmIOc07VM2kPm6m3j/U6jgxyUgDm2Rgh2c1PPy0JUHoQRdoh86hOym0bHyF6G1T6sn+N5lildhvl/T59H5KVyA==" + }, "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", @@ -1706,6 +1760,14 @@ "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.0.tgz", "integrity": "sha512-xTCx2vohXC2EWWDqY/zb4+5Mu28D+HYNSOuFzsyRDRvI/e1ICb69afwaUwfjr+25ZXldbOLyp+iDUZHq8UnTag==" }, + "sonic-boom": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-0.6.1.tgz", + "integrity": "sha512-3qx6XXDeG+hPNa+jla1H6BMBLcjLl8L8NRERLVeIf/EuPqoqmq4K8owG29Xu7OypT/7/YT/0uKW6YitsKA+nLQ==", + "requires": { + "flatstr": "^1.0.5" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", diff --git a/src/currencyservice/package.json b/src/currencyservice/package.json index 1d89148..7a21752 100644 --- a/src/currencyservice/package.json +++ b/src/currencyservice/package.json @@ -17,6 +17,7 @@ "google-protobuf": "^3.0.0", "grpc": "^1.0.0", "left-pad": "^1.3.0", + "pino": "^5.6.2", "request": "^2.87.0", "xml2js": "^0.4.19" }, diff --git a/src/currencyservice/server.js b/src/currencyservice/server.js index 69f7c3c..633078c 100644 --- a/src/currencyservice/server.js +++ b/src/currencyservice/server.js @@ -32,6 +32,7 @@ const path = require('path'); const grpc = require('grpc'); const request = require('request'); const xml2js = require('xml2js'); +const pino = require('pino'); const protoLoader = require('@grpc/proto-loader'); const MAIN_PROTO_PATH = path.join(__dirname, './proto/demo.proto'); @@ -43,6 +44,13 @@ const DATA_URL = 'http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml'; const shopProto = _loadProto(MAIN_PROTO_PATH).hipstershop; const healthProto = _loadProto(HEALTH_PROTO_PATH).grpc.health.v1; +const logger = pino({ + name: 'currencyservice-server', + messageKey: 'message', + changeLevelName: 'severity', + useLevelLabels: true +}); + /** * Helper function that loads a protobuf file. */ @@ -67,7 +75,7 @@ function _loadProto (path) { let _data; function _getCurrencyData (callback) { if (!_data) { - console.log('Fetching currency data...'); + logger.info('Fetching currency data...'); request(DATA_URL, (err, res) => { if (err) { throw new Error(`Error getting data: ${err}`); @@ -108,7 +116,7 @@ function _carry (amount) { * Lists the supported currencies */ function getSupportedCurrencies (call, callback) { - console.log('Getting supported currencies...'); + logger.info('Getting supported currencies...'); _getCurrencyData((data) => { callback(null, {currency_codes: Object.keys(data)}); }); @@ -118,7 +126,7 @@ function getSupportedCurrencies (call, callback) { * Converts between currencies */ function convert (call, callback) { - console.log('received conversion request'); + logger.info('received conversion request'); try { _getCurrencyData((data) => { const request = call.request; @@ -142,12 +150,11 @@ function convert (call, callback) { result.nanos = Math.floor(result.nanos); result.currency_code = request.to_code; - console.log(`conversion request successful`); + logger.info(`conversion request successful`); callback(null, result); }); } catch (err) { - console.error('conversion request failed.'); - console.error(err); + logger.error(`conversion request failed: ${err}`); callback(err.message); } } @@ -164,7 +171,7 @@ function check (call, callback) { * CurrencyConverter service at the sample server port */ function main () { - console.log(`Starting gRPC server on port ${PORT}...`); + logger.info(`Starting gRPC server on port ${PORT}...`); const server = new grpc.Server(); server.addService(shopProto.CurrencyService.service, {getSupportedCurrencies, convert}); server.addService(healthProto.Health.service, {check}); diff --git a/src/emailservice/email_client.py b/src/emailservice/email_client.py index e4e591f..32593fe 100644 --- a/src/emailservice/email_client.py +++ b/src/emailservice/email_client.py @@ -19,6 +19,9 @@ import grpc import demo_pb2 import demo_pb2_grpc +from logger import getJSONLogger +logger = getJSONLogger('emailservice-client') + # from opencensus.trace.tracer import Tracer # from opencensus.trace.exporters import stackdriver_exporter # from opencensus.trace.ext.grpc import client_interceptor @@ -39,10 +42,10 @@ def send_confirmation_email(email, order): email = email, order = order )) - print('Request sent.') + logger.info('Request sent.') except grpc.RpcError as err: - print(err.details()) - print('{}, {}'.format(err.code().name, err.code().value)) + logger.error(err.details()) + logger.error('{}, {}'.format(err.code().name, err.code().value)) if __name__ == '__main__': - print('Client for email service.') \ No newline at end of file + logger.info('Client for email service.') diff --git a/src/emailservice/email_server.py b/src/emailservice/email_server.py index a2cb073..676b70e 100644 --- a/src/emailservice/email_server.py +++ b/src/emailservice/email_server.py @@ -50,6 +50,9 @@ from grpc_health.v1 import health_pb2_grpc # except: # pass +from logger import getJSONLogger +logger = getJSONLogger('emailservice-server') + # Loads confirmation email template from file env = Environment( loader=FileSystemLoader('templates'), @@ -78,14 +81,14 @@ class EmailService(BaseEmailService): "from": { "address_spec": from_address, }, - "to": [{ - "address_spec": email_address + "to": [{ + "address_spec": email_address }], "subject": "Your Confirmation Email", "html_body": content } ) - print("Message sent: {}".format(response.rfc822_message_id)) + logger.info("Message sent: {}".format(response.rfc822_message_id)) def SendOrderConfirmation(self, request, context): email = request.email @@ -95,7 +98,7 @@ class EmailService(BaseEmailService): confirmation = template.render(order = order) except TemplateError as err: context.set_details("An error occurred when preparing the confirmation mail.") - print(err.message) + logger.error(err.message) context.set_code(grpc.StatusCode.INTERNAL) return demo_pb2.Empty() @@ -111,7 +114,7 @@ class EmailService(BaseEmailService): class DummyEmailService(BaseEmailService): def SendOrderConfirmation(self, request, context): - print('A request to send order confirmation email to {} has been received.'.format(request.email)) + logger.info('A request to send order confirmation email to {} has been received.'.format(request.email)) return demo_pb2.Empty() class HealthCheck(): @@ -131,7 +134,7 @@ def start(dummy_mode): health_pb2_grpc.add_HealthServicer_to_server(service, server) port = os.environ.get('PORT', "8080") - print("listening on port: "+port) + logger.info("listening on port: "+port) server.add_insecure_port('[::]:'+port) server.start() try: @@ -142,5 +145,5 @@ def start(dummy_mode): if __name__ == '__main__': - print('starting the email service in dummy mode.') + logger.info('starting the email service in dummy mode.') start(dummy_mode = True) diff --git a/src/emailservice/logger.py b/src/emailservice/logger.py new file mode 100644 index 0000000..a7fb527 --- /dev/null +++ b/src/emailservice/logger.py @@ -0,0 +1,40 @@ +#!/usr/bin/python +# +# Copyright 2018 Google LLC +# +# 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 logging +import sys +from pythonjsonlogger import jsonlogger + +# TODO(yoshifumi) this class is duplicated since other Python services are +# not sharing the modules for logging. +class CustomJsonFormatter(jsonlogger.JsonFormatter): + def add_fields(self, log_record, record, message_dict): + super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict) + if not log_record.get('timestamp'): + log_record['timestamp'] = record.created + if log_record.get('severity'): + log_record['severity'] = log_record['severity'].upper() + else: + log_record['severity'] = record.levelname + +def getJSONLogger(name): + logger = logging.getLogger(name) + handler = logging.StreamHandler(sys.stdout) + formatter = CustomJsonFormatter('(timestamp) (severity) (name) (message)') + handler.setFormatter(formatter) + logger.addHandler(handler) + logger.setLevel(logging.INFO) + return logger diff --git a/src/emailservice/requirements.txt b/src/emailservice/requirements.txt index ca3f1c3..37f6f10 100644 --- a/src/emailservice/requirements.txt +++ b/src/emailservice/requirements.txt @@ -32,10 +32,11 @@ pycairo==1.17.1 pycparser==2.19 pycrypto==2.6.1 PyGObject==3.30.1 +python-json-logger==0.1.9 pytz==2018.5 pyxdg==0.26 requests==2.19.1 rsa==4.0 SecretStorage==3.1.0 six==1.11.0 -urllib3==1.23 \ No newline at end of file +urllib3==1.23 diff --git a/src/paymentservice/charge.js b/src/paymentservice/charge.js index a0151ed..ce9674a 100644 --- a/src/paymentservice/charge.js +++ b/src/paymentservice/charge.js @@ -14,6 +14,15 @@ const cardValidator = require('simple-card-validator'); const uuid = require('uuid/v4'); +const pino = require('pino'); + +const logger = pino({ + name: 'paymentservice-charge', + messageKey: 'message', + changeLevelName: 'severity', + useLevelLabels: true +}); + class CreditCardError extends Error { constructor (message) { @@ -67,7 +76,7 @@ module.exports = function charge (request) { const { credit_card_expiration_year: year, credit_card_expiration_month: month } = creditCard; if ((currentYear * 12 + currentMonth) > (year * 12 + month)) { throw new ExpiredCreditCard(cardNumber.replace('-', ''), month, year); } - console.log(`Transaction processed: ${cardType} ending ${cardNumber.substr(-4)} \ + logger.info(`Transaction processed: ${cardType} ending ${cardNumber.substr(-4)} \ Amount: ${amount.currency_code}${amount.units}.${amount.nanos}`); return { transaction_id: uuid() }; diff --git a/src/paymentservice/package-lock.json b/src/paymentservice/package-lock.json index f7d35a4..7d0e682 100644 --- a/src/paymentservice/package-lock.json +++ b/src/paymentservice/package-lock.json @@ -700,16 +700,36 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" }, + "fast-json-parse": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-json-parse/-/fast-json-parse-1.0.3.tgz", + "integrity": "sha512-FRWsaZRWEJ1ESVNbDWmsAlqDk96gPQezzLghafp5J4GUKjbCz3OkAHuZs5TuPEtkbVQERysLp9xv6c24fBm8Aw==" + }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, + "fast-redact": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-1.2.0.tgz", + "integrity": "sha512-k/uSk9PtFmvYx0m7bRk5B2gZChQk4euWhrn7Mf3vYSmwZBLh7cGNuMuc/vhH1MKMPyVJMMtl9oMwPnwlKqs7CQ==" + }, + "fast-safe-stringify": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.6.tgz", + "integrity": "sha512-q8BZ89jjc+mz08rSxROs8VsrBBcn1SIw1kq9NjolL509tkABRk9io01RAjSaEv1Xb2uFLt8VtRiZbGp5H8iDtg==" + }, "findit2": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/findit2/-/findit2-2.2.3.tgz", "integrity": "sha1-WKRmaX34piBc39vzlVNri9d3pfY=" }, + "flatstr": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.8.tgz", + "integrity": "sha512-YXblbv/vc1zuVVUtnKl1hPqqk7TalZCppnKE7Pr8FI/Rp48vzckS/4SJ4Y9O9RNiI82Vcw/FydmtqdQOg1Dpqw==" + }, "follow-redirects": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.1.tgz", @@ -1592,6 +1612,26 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" }, + "pino": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/pino/-/pino-5.6.2.tgz", + "integrity": "sha512-JVMYqJkE58b2u5+t85zJLbUDOhoWDjEQqrRY1eQzuR4Ub9RIyUSQJms4deT8Joy+C/QIdrrie7NffgCm+ao9xw==", + "requires": { + "fast-json-parse": "^1.0.3", + "fast-redact": "^1.2.0", + "fast-safe-stringify": "^2.0.6", + "flatstr": "^1.0.5", + "pino-std-serializers": "^2.2.1", + "pump": "^3.0.0", + "quick-format-unescaped": "^3.0.0", + "sonic-boom": "^0.6.1" + } + }, + "pino-std-serializers": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-2.2.1.tgz", + "integrity": "sha512-QqL7kkF7eMCpFG4hpZD8UPQga/kxkkh3E62HzMzTIL4OQyijyisAnBL8msBEAml8xcb/ioGhH7UUzGxuHqczhQ==" + }, "pretty-ms": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-3.2.0.tgz", @@ -1621,6 +1661,15 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", @@ -1631,6 +1680,11 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, + "quick-format-unescaped": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-3.0.0.tgz", + "integrity": "sha512-XmIOc07VM2kPm6m3j/U6jgxyUgDm2Rgh2c1PPy0JUHoQRdoh86hOym0bHyF6G1T6sn+N5lildhvl/T59H5KVyA==" + }, "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", @@ -1728,6 +1782,14 @@ "resolved": "https://registry.npmjs.org/simple-card-validator/-/simple-card-validator-1.1.0.tgz", "integrity": "sha1-675uRp/q7Cy7SBX4Qyu+BMccNvk=" }, + "sonic-boom": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-0.6.1.tgz", + "integrity": "sha512-3qx6XXDeG+hPNa+jla1H6BMBLcjLl8L8NRERLVeIf/EuPqoqmq4K8owG29Xu7OypT/7/YT/0uKW6YitsKA+nLQ==", + "requires": { + "flatstr": "^1.0.5" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", diff --git a/src/paymentservice/package.json b/src/paymentservice/package.json index c62033b..b977be4 100644 --- a/src/paymentservice/package.json +++ b/src/paymentservice/package.json @@ -16,6 +16,7 @@ "@google-cloud/trace-agent": "^2.11.0", "@grpc/proto-loader": "^0.1.0", "grpc": "^1.12.3", + "pino": "^5.6.2", "simple-card-validator": "^1.1.0", "uuid": "^3.2.1" }, diff --git a/src/paymentservice/server.js b/src/paymentservice/server.js index ebce323..11598b7 100644 --- a/src/paymentservice/server.js +++ b/src/paymentservice/server.js @@ -14,10 +14,18 @@ const path = require('path'); const grpc = require('grpc'); +const pino = require('pino'); const protoLoader = require('@grpc/proto-loader'); const charge = require('./charge'); +const logger = pino({ + name: 'paymentservice-server', + messageKey: 'message', + changeLevelName: 'severity', + useLevelLabels: true +}); + class HipsterShopServer { constructor (protoRoot, port = HipsterShopServer.DEFAULT_PORT) { this.port = port; @@ -38,7 +46,7 @@ class HipsterShopServer { */ static ChargeServiceHandler (call, callback) { try { - console.log(`PaymentService#Charge invoked with request ${JSON.stringify(call.request)}`); + logger.info(`PaymentService#Charge invoked with request ${JSON.stringify(call.request)}`); const response = charge(call.request); callback(null, response); } catch (err) { @@ -53,7 +61,7 @@ class HipsterShopServer { listen () { this.server.bind(`0.0.0.0:${this.port}`, grpc.ServerCredentials.createInsecure()); - console.log(`PaymentService grpc server listening on ${this.port}`); + logger.info(`PaymentService grpc server listening on ${this.port}`); this.server.start(); } diff --git a/src/recommendationservice/client.py b/src/recommendationservice/client.py index d24afee..7894886 100644 --- a/src/recommendationservice/client.py +++ b/src/recommendationservice/client.py @@ -14,15 +14,18 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sys import grpc import demo_pb2 import demo_pb2_grpc -import sys from opencensus.trace.tracer import Tracer from opencensus.trace.exporters import stackdriver_exporter from opencensus.trace.ext.grpc import client_interceptor +from logger import getJSONLogger +logger = getJSONLogger('recommendationservice-server') + if __name__ == "__main__": # get port if len(sys.argv) > 1: @@ -45,4 +48,4 @@ if __name__ == "__main__": request = demo_pb2.ListRecommendationsRequest(user_id="test", product_ids=["test"]) # make call to server response = stub.ListRecommendations(request) - print(response) \ No newline at end of file + logger.info(response) diff --git a/src/recommendationservice/logger.py b/src/recommendationservice/logger.py new file mode 100644 index 0000000..a7fb527 --- /dev/null +++ b/src/recommendationservice/logger.py @@ -0,0 +1,40 @@ +#!/usr/bin/python +# +# Copyright 2018 Google LLC +# +# 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 logging +import sys +from pythonjsonlogger import jsonlogger + +# TODO(yoshifumi) this class is duplicated since other Python services are +# not sharing the modules for logging. +class CustomJsonFormatter(jsonlogger.JsonFormatter): + def add_fields(self, log_record, record, message_dict): + super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict) + if not log_record.get('timestamp'): + log_record['timestamp'] = record.created + if log_record.get('severity'): + log_record['severity'] = log_record['severity'].upper() + else: + log_record['severity'] = record.levelname + +def getJSONLogger(name): + logger = logging.getLogger(name) + handler = logging.StreamHandler(sys.stdout) + formatter = CustomJsonFormatter('(timestamp) (severity) (name) (message)') + handler.setFormatter(formatter) + logger.addHandler(handler) + logger.setLevel(logging.INFO) + return logger diff --git a/src/recommendationservice/recommendation_server.py b/src/recommendationservice/recommendation_server.py index b897f73..09e6e0d 100644 --- a/src/recommendationservice/recommendation_server.py +++ b/src/recommendationservice/recommendation_server.py @@ -18,8 +18,8 @@ import grpc from concurrent import futures import time import traceback -import random import os +import random import googleclouddebugger import demo_pb2 @@ -27,6 +27,8 @@ import demo_pb2_grpc from grpc_health.v1 import health_pb2 from grpc_health.v1 import health_pb2_grpc +from logger import getJSONLogger +logger = getJSONLogger('recommendationservice-server') # TODO(morganmclean,ahmetb) tracing currently disabled due to memory leak (see TODO below) # from opencensus.trace.ext.grpc import server_interceptor @@ -47,7 +49,7 @@ class RecommendationService(demo_pb2_grpc.RecommendationServiceServicer): indices = random.sample(range(num_products), num_return) # fetch product ids from indices prod_list = [filtered_products[i] for i in indices] - print("[Recv ListRecommendations] product_ids={}".format(prod_list)) + logger.info("[Recv ListRecommendations] product_ids={}".format(prod_list)) # build and return response response = demo_pb2.ListRecommendationsResponse() response.product_ids.extend(prod_list) @@ -59,7 +61,7 @@ class RecommendationService(demo_pb2_grpc.RecommendationServiceServicer): if __name__ == "__main__": - print("initializing recommendationservice") + logger.info("initializing recommendationservice") # TODO(morganmclean,ahmetb) enabling the tracing interceptor/sampler below # causes an unbounded memory leak eventually OOMing the container. @@ -77,15 +79,15 @@ if __name__ == "__main__": version='1.0.0' ) except Exception, err: - print("could not enable debugger") - traceback.print_exc() + logger.error("could not enable debugger") + logger.error(traceback.print_exc()) pass port = os.environ.get('PORT', "8080") catalog_addr = os.environ.get('PRODUCT_CATALOG_SERVICE_ADDR', '') if catalog_addr == "": raise Exception('PRODUCT_CATALOG_SERVICE_ADDR environment variable not set') - print("product catalog address: " + catalog_addr) + logger.info("product catalog address: " + catalog_addr) channel = grpc.insecure_channel(catalog_addr) product_catalog_stub = demo_pb2_grpc.ProductCatalogServiceStub(channel) @@ -98,7 +100,7 @@ if __name__ == "__main__": health_pb2_grpc.add_HealthServicer_to_server(service, server) # start server - print("listening on port: " + port) + logger.info("listening on port: " + port) server.add_insecure_port('[::]:'+port) server.start() diff --git a/src/recommendationservice/requirements.txt b/src/recommendationservice/requirements.txt index 470d93c..7e77517 100644 --- a/src/recommendationservice/requirements.txt +++ b/src/recommendationservice/requirements.txt @@ -20,6 +20,7 @@ opencensus==0.1.5 protobuf==3.5.2.post1 pyasn1==0.4.3 pyasn1-modules==0.2.2 +python-json-logger==0.1.9 pytz==2018.5 PyYAML==3.13 requests==2.19.1