Skip to content

Commit 79c7eef

Browse files
authored
Merge pull request #111 from fnproject/logging-context
Python logging enhancements
2 parents 5a2b396 + 67f8ed3 commit 79c7eef

7 files changed

Lines changed: 90 additions & 42 deletions

File tree

README.md

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,12 @@ You can set outbound HTTP headers and the HTTP status of the request using `ctx.
2929
- e.g. `ctx.SetResponseHeaders({"Location","http://example.com/","My-Header2": ["v1","v2"]}, 302)`
3030
- or by passing these to the Response object :
3131
```python
32-
return new Response(ctx,
33-
headers={"Location","http://example.com/","My-Header2": ["v1","v2"]},
34-
response_data="Page moved",
35-
status_code=302)
36-
```
32+
return new Response(
33+
ctx,
34+
headers={"Location","http://example.com/","My-Header2": ["v1","v2"]},
35+
response_data="Page moved",
36+
status_code=302)
37+
```
3738

3839
e.g. to redirect users to a different page :
3940
```python
@@ -171,11 +172,11 @@ PASSED
171172

172173
To add coverage first install one more package:
173174
```bash
174-
pip install pytest-cov
175+
pip install pytest-cov
175176
```
176177
then run tests with coverage flag:
177178
```bash
178-
pytest -v -s --tb=long --cov=func func.py
179+
pytest -v -s --tb=long --cov=func func.py
179180
```
180181

181182
```bash
@@ -208,15 +209,15 @@ func.py 19 1 95%
208209

209210
Create a virtualenv:
210211
```bash
211-
python3 -m venv .venv
212+
python3 -m venv .venv
212213
```
213214
Activate virtualenv:
214215
```bash
215-
source .venv/bin/activate
216+
source .venv/bin/activate
216217
```
217218
All you have to do is:
218219
```bash
219-
pip install fdk
220+
pip install fdk
220221
```
221222
Now you have a new tools added!
222223

@@ -236,7 +237,7 @@ This is an entry point to a function, this tool you'd be using while working wit
236237
`fdk` is a Python CLI script that has the following signature:
237238

238239
```bash
239-
fdk <path-to-a-function-module> [module-entrypoint]
240+
fdk <path-to-a-function-module> [module-entrypoint]
240241
```
241242

242243
where:
@@ -254,6 +255,20 @@ fdk func.py
254255
the CLI will look for `handler` Python function.
255256
In order to override `[module-entrypoint]` you need to specify your custom entry point.
256257

258+
### Testing locally
259+
260+
To run a function locally (outside Docker) you need to set `FN_FORMAT` and `FN_LISTENER`, like so:
261+
262+
```bash
263+
env FDK_DEBUG=1 FN_FORMAT=http-stream FN_LISTENER=unix://tmp/func.sock fdk <path-to-a-function-module> [module-entrypoint]
264+
```
265+
266+
You can then test with curl:
267+
268+
```bash
269+
curl -v --unix-socket /tmp/func.sock -H "Fn-Call-Id: 0000000000000000" -H "Fn-Deadline: 2030-01-01T00:00:00.000Z" -XPOST http://function/call -d '{"name":"Tubbs"}'
270+
```
271+
257272
## CLI tool: `fdk-tcp-debug`
258273

259274
The reason why this tool exists is to give a chance to developers to debug their function on their machines.
@@ -265,7 +280,7 @@ when this tool works on top of TCP socket, so, the difference is a transport, no
265280
`fdk-tcp-debug` is a Python CLI script that has the following signature:
266281

267282
```bash
268-
fdk-tcp-debug <port> <path-to-a-function-module> [module-entrypoint]
283+
fdk-tcp-debug <port> <path-to-a-function-module> [module-entrypoint]
269284
```
270285

271286
The behaviour of this CLI is the same, but it will start an FDK on top of the TCP socket.
@@ -292,14 +307,14 @@ In order to test an FDK changes do the following:
292307
Test an FDK change with sample function using `fdk-tcp-debug`:
293308

294309
```bash
295-
pip install -e .
296-
FDK_DEBUG=1 fdk-tcp-debug 5001 samples/echo/func.py handler
310+
pip install -e .
311+
FDK_DEBUG=1 fdk-tcp-debug 5001 samples/echo/func.py handler
297312
```
298313

299314
Then just do:
300315

301316
```bash
302-
curl -v -X POST localhost:5001 -d '{"name":"denis"}'
317+
curl -v -X POST localhost:5001 -d '{"name":"denis"}'
303318
```
304319

305320
### Testing within a function
@@ -331,16 +346,16 @@ ENTRYPOINT ["/python/bin/fdk", "/function/func.py", "handler"]
331346

332347
Build an FDK wheel:
333348
```bash
334-
pip install wheel
335-
PBR_VERSION=test python setup.py bdist_wheel
349+
pip install wheel
350+
PBR_VERSION=test python setup.py bdist_wheel
336351
```
337352

338353
Move an FDK wheel (located at `dist/fdk-test-py3-none-any.whl`) into a function's folder.
339354

340355
Do the deploy:
341356
```bash
342-
fn --versbose deploy --app testapp --local --no-bump
343-
fn config fn testapp test-function FDK_DEBUG 1
357+
fn --versbose deploy --app testapp --local --no-bump
358+
fn config fn testapp test-function FDK_DEBUG 1
344359
```
345360

346361
And the last step - invoke it and see how it goes:
@@ -354,7 +369,7 @@ FDK is based on the asyncio event loop. Default event loop is not quite fast, bu
354369
In order to make an FDK to process IO operation at least 4 times faster you need to add another dependency to your function:
355370

356371
```text
357-
uvloop
372+
uvloop
358373
```
359374

360375
[UVLoop](https://github.com/MagicStack/uvloop) is a CPython wrapper on top of cross-platform [libuv](https://github.com/libuv/libuv).
@@ -372,8 +387,8 @@ A new FDK is here which means there suppose to be a way to upgrade your code fro
372387
As you noticed - an entry point a function changed, i.e., func.py no longer considered as the main module (`__main__`) which means that the following section:
373388

374389
```python
375-
if __name__ == "__main__":
376-
fdk.handle(handler)
390+
if __name__ == "__main__":
391+
fdk.handle(handler)
377392
```
378393

379394
has no effect any longer. Please note that FDK will fail-fast with an appropriate message if old-style FDK format used.
@@ -389,10 +404,10 @@ data = data.read()
389404
If you've been using json lib to turn an incoming data into a dictionary you need to replace: `json.loads` with `json.load`
390405

391406
```python
392-
try:
393-
dct = json.load(data)
394-
except ValueError as ex:
395-
# do here whatever is reasonable
407+
try:
408+
dct = json.load(data)
409+
except ValueError as ex:
410+
# do here whatever is reasonable
396411
```
397412

398413
### Dockerfile
@@ -404,13 +419,13 @@ If you've been using custom multi-stage Dockerfile (derived from what Fn CLI gen
404419
the only thing that is necessary to change is an `ENTRYPOINT` from:
405420

406421
```text
407-
ENTRYPOINT["python", "func.py"]
422+
ENTRYPOINT["python", "func.py"]
408423
```
409424

410425
to:
411426

412427
```text
413-
ENTRYPOINT["/python/bin/fdk", "func.py", "handler"]
428+
ENTRYPOINT["/python/bin/fdk", "func.py", "handler"]
414429
```
415430

416431
If you've been using your own Dockerfile that wasn't derived from the Dockerfile

fdk/async_http/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ async def handle_request(self, request, write_callback, stream_callback):
7474

7575
request.uri_template = uri
7676
response = handler(request)
77-
logger.info("got response from function")
77+
logger.debug("got response from function")
7878
res = await response
7979
body = res.body
8080
headers = res.headers

fdk/async_http/protocol.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ def keep_alive_timeout_callback(self):
251251
time_left, self.keep_alive_timeout_callback
252252
)
253253
else:
254-
logger.info("KeepAlive Timeout. Closing connection.")
254+
logger.debug("KeepAlive Timeout. Closing connection.")
255255
self.transport.close()
256256
self.transport = None
257257

fdk/async_http/server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ def start_serving():
203203
def start():
204204
pid = os.getpid()
205205
try:
206-
logger.info("Starting worker [%s]", pid)
206+
logger.debug("Starting worker [%s]", pid)
207207
if constants.is_py37():
208208
loop.run_until_complete(http_server.serve_forever())
209209
else:

fdk/event_handler.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import sys
1919

2020
from fdk import constants
21+
from fdk import log
2122

2223
from fdk.async_http import response
2324

@@ -36,13 +37,13 @@ def event_handle(handle_code):
3637
"""
3738
async def pure_handler(request):
3839
from fdk import runner
39-
logger.info("in pure_handler")
40+
log.log("in pure_handler")
4041
headers = dict(request.headers)
4142
log_frame_header(headers)
4243
func_response = await runner.handle_request(
4344
handle_code, constants.HTTPSTREAM,
4445
headers=headers, data=io.BytesIO(request.body))
45-
logger.info("request execution completed")
46+
log.log("request execution completed")
4647

4748
headers = func_response.context().GetResponseHeaders()
4849
status = func_response.status()

fdk/log.py

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,46 @@
1515
import logging
1616
import os
1717
import sys
18+
from contextvars import ContextVar
19+
20+
21+
__fn_request_id__ = ContextVar("fn_request_id", default=None)
22+
23+
24+
def set_request_id(rid):
25+
__fn_request_id__.set(rid)
26+
27+
28+
class RequestFormatter(logging.Formatter):
29+
def format(self, record):
30+
record.fn_request_id = __fn_request_id__.get()
31+
return super().format(record)
1832

1933

2034
def __setup_logger():
21-
logging.getLogger("asyncio").setLevel(logging.DEBUG)
35+
fdk_debug = os.environ.get("FDK_DEBUG") in [
36+
'true', '1', 't', 'y', 'yes', 'yeah', 'yup', 'certainly', 'uh-huh']
37+
38+
logging.getLogger("asyncio").setLevel(logging.WARNING)
2239
root = logging.getLogger()
2340
root.setLevel(logging.DEBUG)
41+
2442
ch = logging.StreamHandler(sys.stderr)
25-
formatter = logging.Formatter(
26-
'%(asctime)s - '
43+
formatter = RequestFormatter(
44+
'%(fn_request_id)s - '
2745
'%(name)s - '
2846
'%(levelname)s - '
2947
'%(message)s'
3048
)
3149
ch.setFormatter(formatter)
3250
root.addHandler(ch)
33-
return root
51+
logger = logging.getLogger("fdk")
52+
if fdk_debug:
53+
logger.setLevel(logging.DEBUG)
54+
else:
55+
logger.setLevel(logging.WARNING)
56+
57+
return logger
3458

3559

3660
__log__ = __setup_logger()
@@ -41,7 +65,11 @@ def get_logger():
4165

4266

4367
def log(message):
44-
fdk_debug = os.environ.get("FDK_DEBUG")
45-
if fdk_debug in ['true', '1', 't', 'y', 'yes',
46-
'yeah', 'yup', 'certainly', 'uh-huh']:
47-
__log__.info(message)
68+
__log__.debug(message)
69+
70+
71+
__request_log__ = logging.getLogger('fn')
72+
73+
74+
def get_request_log():
75+
return __request_log__

fdk/runner.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ async def handle_request(handler_code, format_def, **kwargs):
8181
"""
8282
log.log("in handle_request")
8383
ctx, body = context.context_from_format(format_def, **kwargs)
84+
log.set_request_id(ctx.CallID())
8485
log.log("context provisioned")
8586
try:
8687
response_data = await with_deadline(ctx, handler_code, body)
@@ -96,5 +97,8 @@ async def handle_request(handler_code, format_def, **kwargs):
9697

9798
except (Exception, TimeoutError) as ex:
9899
log.log("exception appeared: {0}".format(ex))
99-
traceback.print_exc(file=sys.stderr)
100+
(exctype, value, tb) = sys.exc_info()
101+
tb_flat = ''.join(
102+
s.replace('\n', '\\n') for s in traceback.format_tb(tb))
103+
log.get_request_log().error('{}:{}'.format(value, tb_flat))
100104
return errors.DispatchException(ctx, 502, str(ex)).response()

0 commit comments

Comments
 (0)