├── README.md
├── client
└── client.html
├── imgs
└── app_demo.gif
├── requirements.txt
└── server
├── program.py
└── server.py
/README.md:
--------------------------------------------------------------------------------
1 | ## Realtime Log Streaming with FastAPI and Server-Sent Events
2 |
3 | 
4 |
5 |
6 | ### Setup
7 |
8 | #### Prerequisites
9 | To run this program you will need python 3 (I used 3.8). Then run the following commands:
10 | - python3 -m venv py38
11 | - source py38/bin/activate
12 | - pip install -r requirements.txt
13 |
14 | #### Running the code
15 | - python server/program.py (runs the app that generates logs)
16 | - python server/server.py (runs the web server that sends SSE)
17 | - open client/client.html in a browser to view the events
--------------------------------------------------------------------------------
/client/client.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
15 |
16 |
17 |
18 |
19 | Server Logs:
20 |
21 |
22 |
23 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/imgs/app_demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amittallapragada/SSELoggerDemo/9cbddf3d87f4be4db64245424835202f2492afc8/imgs/app_demo.gif
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | click==7.1.2
2 | fastapi==0.63.0
3 | h11==0.11.0
4 | pydantic==1.7.3
5 | sh==1.14.1
6 | sse-starlette==0.6.2
7 | starlette==0.13.6
8 | uvicorn==0.13.2
9 |
--------------------------------------------------------------------------------
/server/program.py:
--------------------------------------------------------------------------------
1 | """
2 | program.py
3 | This script will keep adding logs to our logger file.
4 | """
5 |
6 | import logging
7 | import time
8 | import os
9 | # create logger with log app
10 | real_path = os.path.realpath(__file__)
11 | dir_path = os.path.dirname(real_path)
12 | LOGFILE = f"{dir_path}/test.log"
13 | logger = logging.getLogger('log_app')
14 | logger.setLevel(logging.DEBUG)
15 | fh = logging.FileHandler(LOGFILE)
16 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
17 | fh.setFormatter(formatter)
18 | logger.addHandler(fh)
19 |
20 | #infinite while loop printing to our log file.
21 | i = 0
22 | while True:
23 | logger.info(f"log message num: {i}")
24 | i += 1
25 | time.sleep(0.3)
--------------------------------------------------------------------------------
/server/server.py:
--------------------------------------------------------------------------------
1 | """
2 | server.py
3 | This script will launch a web server on port 8000 which sends SSE events anytime
4 | logs are added to our log file.
5 | """
6 |
7 | from fastapi import FastAPI, Request
8 | from sse_starlette.sse import EventSourceResponse
9 | from datetime import datetime
10 | import uvicorn
11 | from sh import tail
12 | from fastapi.middleware.cors import CORSMiddleware
13 | import time
14 | import os
15 | #create our app instance
16 | app = FastAPI()
17 |
18 | #add CORS so our web page can connect to our api
19 | app.add_middleware(
20 | CORSMiddleware,
21 | allow_origins=["*"],
22 | allow_credentials=True,
23 | allow_methods=["*"],
24 | allow_headers=["*"],
25 | )
26 | real_path = os.path.realpath(__file__)
27 | dir_path = os.path.dirname(real_path)
28 | LOGFILE = f"{dir_path}/test.log"
29 | #This async generator will listen to our log file in an infinite while loop (happens in the tail command)
30 | #Anytime the generator detects a new line in the log file, it will yield it.
31 | async def logGenerator(request):
32 | for line in tail("-f", LOGFILE, _iter=True):
33 | if await request.is_disconnected():
34 | print("client disconnected!!!")
35 | break
36 | yield line
37 | time.sleep(0.5)
38 |
39 | #This is our api endpoint. When a client subscribes to this endpoint, they will recieve SSE from our log file
40 | @app.get('/stream-logs')
41 | async def runStatus(request: Request):
42 | event_generator = logGenerator(request)
43 | return EventSourceResponse(event_generator)
44 |
45 | #run the app
46 | uvicorn.run(app, host="0.0.0.0", port=8000, debug=True)
47 |
--------------------------------------------------------------------------------