Skip to content

Commit c7058c5

Browse files
committed
feat: create a logger class
1 parent c444cf1 commit c7058c5

File tree

12 files changed

+168
-12
lines changed

12 files changed

+168
-12
lines changed

README.md

+18
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,21 @@ Open browser at https://localhost:3000 and accept invalid cert (may not work in
4545
```sh
4646
yarn test
4747
```
48+
49+
## Logging
50+
51+
Client-side logs are disabled by default in production builds and enabled by default in a development environment. In production, logging can be turned on by adding a couple keys to your browser's `localStorage`. Simply run these two JS statements in you browser's DevTools console:
52+
53+
```
54+
localStorage.setItem('debug', '*'); localStorage.setItem('debug-level', 'debug');
55+
```
56+
57+
The value for `debug` is a namespace filter which determines which portions of the app to display logs for. The namespaces currently used by the app are as follows:
58+
59+
- `main`: logs general application messages
60+
- `action`: logs all actions that modify the internal application state
61+
- `grpc`: logs all GRPC API requests and responses
62+
63+
Example filters: `main,action` will only log main and action messages. `*,-actions` will log everything except action messages.
64+
65+
The value for `debug-level` determines the verbosity of the logs. The value can be one of `debug`, `info`, `warn`, or `error`.

app/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"proxy": "https://localhost:8443",
1616
"dependencies": {
1717
"@improbable-eng/grpc-web": "0.12.0",
18+
"debug": "4.1.1",
1819
"i18next": "19.4.1",
1920
"i18next-browser-languagedetector": "4.0.2",
2021
"mobx": "5.15.4",
@@ -28,6 +29,7 @@
2829
"@testing-library/jest-dom": "^4.2.4",
2930
"@testing-library/react": "^9.3.2",
3031
"@testing-library/user-event": "^7.1.2",
32+
"@types/debug": "4.1.5",
3133
"@types/google-protobuf": "3.7.2",
3234
"@types/jest": "^24.0.0",
3335
"@types/node": "^12.0.0",

app/src/action/channel.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { action } from 'mobx';
1+
import { action, toJS } from 'mobx';
2+
import { actionLog as log } from 'util/log';
23
import LndApi from 'api/lnd';
34
import { Store } from 'store';
45

@@ -19,6 +20,7 @@ class ChannelAction {
1920
* fetch channels from the LND RPC
2021
*/
2122
@action.bound async getChannels() {
23+
log.info('fetching channels');
2224
const channels = await this._lnd.listChannels();
2325
this._store.channels = channels.channelsList.map(c => ({
2426
chanId: c.chanId,
@@ -29,6 +31,7 @@ class ChannelAction {
2931
uptime: c.uptime,
3032
active: c.active,
3133
}));
34+
log.info('updated store.channels', toJS(this._store.channels));
3235
}
3336
}
3437

app/src/action/node.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { action } from 'mobx';
1+
import { action, toJS } from 'mobx';
2+
import { actionLog as log } from 'util/log';
23
import LndApi from 'api/lnd';
34
import { Store } from 'store';
45

@@ -19,7 +20,9 @@ class NodeAction {
1920
* fetch node info from the LND RPC
2021
*/
2122
@action.bound async getInfo() {
23+
log.info('fetching node information');
2224
this._store.info = await this._lnd.getInfo();
25+
log.info('updated store.info', toJS(this._store.info));
2326
}
2427
}
2528

app/src/action/swap.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { action } from 'mobx';
1+
import { action, toJS } from 'mobx';
22
import { SwapState, SwapType } from 'types/generated/loop_pb';
3+
import { actionLog as log } from 'util/log';
34
import LoopApi from 'api/loop';
45
import { Store } from 'store';
56

@@ -20,6 +21,7 @@ class SwapAction {
2021
* fetch swaps from the Loop RPC
2122
*/
2223
@action.bound async listSwaps() {
24+
log.info('fetching Loop history');
2325
const loopSwaps = await this._loop.listSwaps();
2426
this._store.swaps = loopSwaps.swapsList
2527
// sort the list with newest first as the API returns them out of order
@@ -31,6 +33,7 @@ class SwapAction {
3133
createdOn: new Date(s.initiationTime / 1000 / 1000),
3234
status: this._stateToString(s.state),
3335
}));
36+
log.info('updated store.swaps', toJS(this._store.swaps));
3437
}
3538

3639
/**

app/src/api/grpc.ts

+9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ProtobufMessage } from '@improbable-eng/grpc-web/dist/typings/message';
33
import { Metadata } from '@improbable-eng/grpc-web/dist/typings/metadata';
44
import { UnaryMethodDefinition } from '@improbable-eng/grpc-web/dist/typings/service';
55
import { DEV_HOST } from 'config';
6+
import { grpcLog as log } from 'util/log';
67

78
/**
89
* Executes a single GRPC request and returns a promise which will resolve with the response
@@ -16,16 +17,24 @@ export const grpcRequest = <TReq extends ProtobufMessage, TRes extends ProtobufM
1617
metadata?: Metadata.ConstructorArg,
1718
): Promise<TRes> => {
1819
return new Promise((resolve, reject) => {
20+
log.debug(
21+
`Request: ${methodDescriptor.service.serviceName}.${methodDescriptor.methodName}`,
22+
);
23+
log.debug(` - req: `, request.toObject());
1924
grpc.unary(methodDescriptor, {
2025
host: DEV_HOST,
2126
request,
2227
metadata,
2328
onEnd: ({ status, statusMessage, headers, message, trailers }) => {
29+
log.debug(' - status', status, statusMessage);
30+
log.debug(' - headers', headers);
2431
if (status === grpc.Code.OK && message) {
32+
log.debug(' - message', message.toObject());
2533
resolve(message as TRes);
2634
} else {
2735
reject(new Error(`${status}: ${statusMessage}`));
2836
}
37+
log.debug(' - trailers', trailers);
2938
},
3039
});
3140
});

app/src/api/lnd.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class LndApi {
1616
* call the LND `GetInfo` RPC and return the response
1717
*/
1818
async getInfo(): Promise<LND.GetInfoResponse.AsObject> {
19-
const req = new LND.GetInfoResponse();
19+
const req = new LND.GetInfoRequest();
2020
const res = await grpcRequest(Lightning.GetInfo, req, this._meta);
2121
return res.toObject();
2222
}

app/src/config.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,7 @@ export const DEV_MACAROON =
33
'0201036c6e6402eb01030a109675b859a9f26856dc8447e4df3236521201301a160a0761646472657373120472656164120577726974651a130a04696e666f120472656164120577726974651a170a08696e766f69636573120472656164120577726974651a140a086d616361726f6f6e120867656e65726174651a160a076d657373616765120472656164120577726974651a170a086f6666636861696e120472656164120577726974651a160a076f6e636861696e120472656164120577726974651a140a057065657273120472656164120577726974651a180a067369676e6572120867656e657261746512047265616400000620fe4ae4ba5086f99902ba9ad08ca21f3db79faae8fc1190e92eb50ab145d99613';
44

55
// the GRPC server to make requests to
6-
export const DEV_HOST = 'http://localhost:3000';
6+
export const DEV_HOST =
7+
process.env.NODE_ENV === 'production'
8+
? 'https://localhost:8443'
9+
: 'http://localhost:3000';

app/src/i18n/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ const config: InitOptions = {
4343
interpolation: {
4444
escapeValue: false,
4545
},
46+
detection: {
47+
lookupLocalStorage: 'lang',
48+
},
4649
};
4750

4851
i18n.use(LanguageDetector).use(initReactI18next).init(config);

app/src/index.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ import ReactDOM from 'react-dom';
33
import 'mobx-react/batchingForReactDom';
44
import './i18n';
55
import './index.css';
6+
import { log } from 'util/log';
67
import App from './App';
78

9+
log.info('Rendering the App');
10+
811
ReactDOM.render(
912
<React.StrictMode>
1013
<App />

app/src/util/log.ts

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import debug, { Debugger } from 'debug';
2+
3+
enum LogLevel {
4+
debug = 1,
5+
info = 2,
6+
warn = 3,
7+
error = 4,
8+
none = 5,
9+
}
10+
11+
const prefixes = {
12+
[LogLevel.debug]: 'DEBUG',
13+
[LogLevel.info]: 'INFO',
14+
[LogLevel.warn]: 'WARN',
15+
[LogLevel.error]: 'ERROR',
16+
[LogLevel.none]: '',
17+
};
18+
19+
/**
20+
* A logger class with support to skip
21+
*/
22+
class Logger {
23+
private _levelToOutput: LogLevel;
24+
private _logger: Debugger;
25+
26+
constructor(levelToOutput: LogLevel, namespace: string) {
27+
this._levelToOutput = levelToOutput;
28+
this._logger = debug(namespace);
29+
}
30+
31+
/**
32+
* creates a new Logger instance by inspecting the executing environment
33+
*/
34+
static fromEnv(namespace: string): Logger {
35+
// by default, log everything in development and nothing in production
36+
let level = process.env.NODE_ENV !== 'production' ? LogLevel.debug : LogLevel.none;
37+
38+
if (localStorage.getItem('debug')) {
39+
// if a 'debug' key is found in localStorage, use the level in storage or 'debug' by default
40+
const storageLevel = localStorage.getItem('debug-level') || 'debug';
41+
level = LogLevel[storageLevel as keyof typeof LogLevel];
42+
} else if (process.env.NODE_ENV !== 'production') {
43+
// if running in development with no localStorage key
44+
level = LogLevel.debug;
45+
// set the keys so they can be easily changed by the dev
46+
localStorage.setItem('debug', '*');
47+
localStorage.setItem('debug-level', 'debug');
48+
}
49+
50+
return new Logger(level, namespace);
51+
}
52+
53+
/**
54+
* log a debug message
55+
*/
56+
debug = (message: string, ...args: any[]) => this._log(LogLevel.debug, message, args);
57+
58+
/**
59+
* log a info message
60+
*/
61+
info = (message: string, ...args: any[]) => this._log(LogLevel.info, message, args);
62+
63+
/**
64+
* log a warn message
65+
*/
66+
warn = (message: string, ...args: any[]) => this._log(LogLevel.warn, message, args);
67+
68+
/**
69+
* log a error message
70+
*/
71+
error = (message: string, ...args: any[]) => this._log(LogLevel.error, message, args);
72+
73+
/**
74+
* A shared logging function which will only out logs based on the level of this Logger instance
75+
* @param level the level of the message being logged
76+
* @param message the message to log
77+
* @param args optional additional arguments to log
78+
*/
79+
private _log(level: LogLevel, message: string, args: any[]) {
80+
// console.warn('this._levelToOutput', this._levelToOutput);
81+
// console.warn('level', level);
82+
// don't log if the level to output is greater than the level of this message
83+
if (this._levelToOutput > level) return;
84+
85+
const prefix = prefixes[level];
86+
this._logger(`[${prefix}] ${message}`, ...args);
87+
// console.log(`[${prefix}] ${message}`, args);
88+
}
89+
}
90+
91+
/**
92+
* the main logger for the app
93+
*/
94+
export const log = Logger.fromEnv('main');
95+
96+
/**
97+
* the logger for GRPC requests and responses
98+
*/
99+
export const grpcLog = Logger.fromEnv('grpc');
100+
101+
/**
102+
* the logger for state updates via mobx actions
103+
*/
104+
export const actionLog = Logger.fromEnv('action');

app/yarn.lock

+12-7
Original file line numberDiff line numberDiff line change
@@ -1355,6 +1355,11 @@
13551355
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
13561356
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
13571357

1358+
1359+
version "4.1.5"
1360+
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd"
1361+
integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==
1362+
13581363
"@types/eslint-visitor-keys@^1.0.0":
13591364
version "1.0.0"
13601365
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
@@ -3463,20 +3468,20 @@ [email protected], debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
34633468
dependencies:
34643469
ms "2.0.0"
34653470

3471+
[email protected], debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
3472+
version "4.1.1"
3473+
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
3474+
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
3475+
dependencies:
3476+
ms "^2.1.1"
3477+
34663478
debug@^3.0.0, debug@^3.1.1, debug@^3.2.5:
34673479
version "3.2.6"
34683480
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
34693481
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
34703482
dependencies:
34713483
ms "^2.1.1"
34723484

3473-
debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
3474-
version "4.1.1"
3475-
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
3476-
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
3477-
dependencies:
3478-
ms "^2.1.1"
3479-
34803485
decamelize@^1.2.0:
34813486
version "1.2.0"
34823487
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"

0 commit comments

Comments
 (0)