├── 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 | ![Demo](/imgs/app_demo.gif?raw=true "Optional Title") 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 | --------------------------------------------------------------------------------