Skip to content

Commit 782c953

Browse files
authored
Merge pull request #51 from steamcmd/improve-logging
Improve logging
2 parents 134fb3f + a70d5c3 commit 782c953

File tree

7 files changed

+123
-52
lines changed

7 files changed

+123
-52
lines changed

.github/workflows/deploy.yml

+4-4
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
runs-on: ubuntu-22.04
2121
needs: check-requirements
2222
steps:
23-
- uses: actions/checkout@v3
23+
- uses: actions/checkout@v4
2424
- name: Parse API Version
2525
run: echo "API_VERSION=$(echo $GITHUB_REF | awk -F '/' '{print $NF}' | cut -c 2-)" >> $GITHUB_ENV
2626
- name: Docker Login
@@ -37,7 +37,7 @@ jobs:
3737
name: Update Readme
3838
runs-on: ubuntu-22.04
3939
steps:
40-
- uses: actions/checkout@v3
40+
- uses: actions/checkout@v4
4141
- name: Update Docker Hub Description
4242
uses: peter-evans/dockerhub-description@v2
4343
env:
@@ -50,7 +50,7 @@ jobs:
5050
runs-on: ubuntu-22.04
5151
needs: [check-requirements, build-image]
5252
steps:
53-
- uses: actions/checkout@v3
53+
- uses: actions/checkout@v4
5454
- uses: superfly/flyctl-actions/setup-flyctl@master
5555
- name: Parse API Version
5656
run: echo "API_VERSION=$(echo $GITHUB_REF | awk -F '/' '{print $NF}' | cut -c 2-)" >> $GITHUB_ENV
@@ -64,7 +64,7 @@ jobs:
6464
runs-on: ubuntu-22.04
6565
needs: [check-requirements, build-image]
6666
steps:
67-
- uses: actions/checkout@v3
67+
- uses: actions/checkout@v4
6868
- name: Parse API Version
6969
run: echo "API_VERSION=$(echo $GITHUB_REF | awk -F '/' '{print $NF}' | cut -c 2-)" >> $GITHUB_ENV
7070
- name: Deploy API on Render.com

.github/workflows/test.yml

+4-13
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,15 @@ jobs:
1313
name: Test Image
1414
runs-on: ubuntu-22.04
1515
steps:
16-
- uses: actions/checkout@v3
16+
- uses: actions/checkout@v4
1717
- name: Build Image
1818
run: docker build -t steamcmd/api:latest .
1919

2020
python-lint:
2121
name: Python Lint
2222
runs-on: ubuntu-22.04
2323
steps:
24-
- uses: actions/checkout@v3
25-
- uses: ricardochaves/python-lint@v1.3.0
24+
- uses: actions/checkout@v4
25+
- uses: jpetrucciani/ruff-check@main
2626
with:
27-
# python files
28-
python-root-list: "src"
29-
# enabled linters
30-
use-black: true
31-
# disabled linters
32-
use-pylint: false
33-
use-pycodestyle: false
34-
use-flake8: false
35-
use-mypy: false
36-
use-isort: false
27+
path: 'src/'

README.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ possible as well.
8484

8585
All the settings are optional. Keep in mind that when you choose a cache type
8686
that you will need to set the corresponding cache settings for that type as well
87-
(ex.: `REDIS_HOST`, `REDIS_PORT`, `REDIS_PASSWORD` is required when using the
88-
**redis** type).
87+
(ex.: `REDIS_HOST`, `REDIS_PORT`, `REDIS_PASSWORD` or `REDIS_URL` is required
88+
when using the **redis** type).
8989

9090
All the available options in an `.env` file:
9191
```
@@ -106,6 +106,9 @@ REDIS_PASSWORD="YourRedisP@ssword!"
106106
# (see: https://redis-py.readthedocs.io/en/stable/#quickly-connecting-to-redis)
107107
REDIS_URL="redis://YourUsername:YourRedisP@ssword!@your.redis.host.example.com:6379"
108108
109+
# logging
110+
LOG_LEVEL=info
111+
109112
# deta
110113
DETA_BASE_NAME="steamcmd"
111114
DETA_PROJECT_KEY="YourDet@ProjectKey!"

docker-compose.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
version: '3'
21
services:
32
web:
43
build: .
@@ -16,6 +15,7 @@ services:
1615
CACHE_EXPIRATION: 120
1716
REDIS_HOST: redis
1817
REDIS_PORT: 6379
18+
LOG_LEVEL: info
1919
PYTHONUNBUFFERED: "TRUE"
2020
restart: always
2121
redis:

requirements.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ fastapi
22
redis
33
deta
44

5-
python-dotenv
65
semver
6+
python-dotenv
7+
logfmter
78

89
steam[client]
910
gevent

src/functions.py

+62-25
Original file line numberDiff line numberDiff line change
@@ -3,51 +3,62 @@
33
"""
44

55
# import modules
6-
import os, json, gevent, datetime, redis
6+
import os
7+
import json
8+
import gevent
9+
import redis
10+
import logging
711
from steam.client import SteamClient
812
from deta import Deta
913

1014

1115
def app_info(app_id):
12-
connect_retries = 3
13-
connect_timeout = 5
14-
current_time = str(datetime.datetime.now())
16+
connect_retries = 2
17+
connect_timeout = 3
18+
19+
logging.info("Started requesting app info", extra={"app_id": app_id})
1520

1621
try:
1722
# Sometimes it hangs for 30+ seconds. Normal connection takes about 500ms
1823
for _ in range(connect_retries):
19-
count = _ + 1
20-
count = str(count)
24+
count = str(_)
2125

2226
try:
2327
with gevent.Timeout(connect_timeout):
24-
print("Connecting via steamclient")
25-
print(
26-
"Retrieving app info for: "
27-
+ str(app_id)
28-
+ ", retry count: "
29-
+ count
28+
logging.info(
29+
"Retrieving app info from steamclient",
30+
extra={"app_id": app_id, "retry_count": count},
3031
)
3132

33+
logging.debug("Connecting via steamclient to steam api")
3234
client = SteamClient()
3335
client.anonymous_login()
3436
client.verbose_debug = False
37+
38+
logging.debug("Requesting app info from steam api")
3539
info = client.get_product_info(apps=[app_id], timeout=1)
3640

3741
return info
3842

3943
except gevent.timeout.Timeout:
44+
logging.warning(
45+
"Encountered timeout when trying to connect to steam api. Retrying.."
46+
)
4047
client._connecting = False
4148

4249
else:
43-
print("Succesfully retrieved app info for app id: " + str(app_id))
50+
logging.info("Succesfully retrieved app info", extra={"app_id": app_id})
4451
break
4552
else:
53+
logging.error(
54+
"Max connect retries exceeded",
55+
extra={"connect_retries": connect_retries},
56+
)
4657
raise Exception(f"Max connect retries ({connect_retries}) exceeded")
4758

4859
except Exception as err:
49-
print("Failed in retrieving app info for app id: " + str(app_id))
50-
print(err)
60+
logging.error("Failed in retrieving app info", extra={"app_id": app_id})
61+
logging.error(err, extra={"app_id": app_id})
5162

5263

5364
def cache_read(app_id):
@@ -61,7 +72,10 @@ def cache_read(app_id):
6172
return deta_read(app_id)
6273
else:
6374
# print query parse error and return empty dict
64-
print("Incorrect set cache type: " + os.environ["CACHE_TYPE"])
75+
logging.error(
76+
"Set incorrect cache type",
77+
extra={"app_id": app_id, "cache_type": os.environ["CACHE_TYPE"]},
78+
)
6579

6680
# return failed status
6781
return False
@@ -78,7 +92,10 @@ def cache_write(app_id, data):
7892
return deta_write(app_id, data)
7993
else:
8094
# print query parse error and return empty dict
81-
print("Incorrect set cache type: " + os.environ["CACHE_TYPE"])
95+
logging.error(
96+
"Set incorrect cache type",
97+
extra={"app_id": app_id, "cache_type": os.environ["CACHE_TYPE"]},
98+
)
8299

83100
# return failed status
84101
return False
@@ -130,13 +147,13 @@ def redis_read(app_id):
130147
# return cached data
131148
return data
132149

133-
except Exception as read_error:
150+
except Exception as redis_error:
134151
# print query parse error and return empty dict
135-
print(
136-
"The following error occured while trying to read and decode "
137-
+ "from Redis cache: \n > "
138-
+ str(read_error)
152+
logging.error(
153+
"An error occured while trying to read and decode from Redis cache",
154+
extra={"app_id": app_id, "error_msg": redis_error},
139155
)
156+
140157
# return failed status
141158
return False
142159

@@ -162,15 +179,35 @@ def redis_write(app_id, data):
162179

163180
except Exception as redis_error:
164181
# print query parse error and return empty dict
165-
print(
166-
"The following error occured while trying to write to Redis cache: \n > "
167-
+ str(redis_error)
182+
logging.error(
183+
"An error occured while trying to write to Redis cache",
184+
extra={"app_id": app_id, "error_msg": redis_error},
168185
)
169186

170187
# return fail status
171188
return False
172189

173190

191+
def log_level(level):
192+
"""
193+
Sets lowest level to log.
194+
"""
195+
196+
match level:
197+
case "debug":
198+
logging.getLogger().setLevel(logging.DEBUG)
199+
case "info":
200+
logging.getLogger().setLevel(logging.INFO)
201+
case "warning":
202+
logging.getLogger().setLevel(logging.WARNING)
203+
case "error":
204+
logging.getLogger().setLevel(logging.ERROR)
205+
case "critical":
206+
logging.getLogger().setLevel(logging.CRITICAL)
207+
case _:
208+
logging.getLogger().setLevel(logging.WARNING)
209+
210+
174211
def deta_read(app_id):
175212
"""
176213
Read app info from Deta base cache.

src/main.py

+45-6
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
"""
44

55
# import modules
6-
from deta import Deta
7-
from typing import Union
8-
from fastapi import FastAPI, Response, status
9-
from functions import app_info, cache_read, cache_write
10-
import os, datetime, json, semver, typing
6+
import os
7+
import json
8+
import semver
9+
import typing
10+
import logging
11+
from fastapi import FastAPI, Response
12+
from functions import app_info, cache_read, cache_write, log_level
13+
from logfmter import Logfmter
1114

1215
# load configuration
1316
from dotenv import load_dotenv
@@ -17,6 +20,15 @@
1720
# initialise app
1821
app = FastAPI()
1922

23+
# set logformat
24+
formatter = Logfmter(keys=["level"], mapping={"level": "levelname"})
25+
handler = logging.StreamHandler()
26+
handler.setFormatter(formatter)
27+
logging.basicConfig(handlers=[handler])
28+
29+
if "LOG_LEVEL" in os.environ:
30+
log_level(os.environ["LOG_LEVEL"])
31+
2032

2133
# include "pretty" for backwards compatibility
2234
class PrettyJSONResponse(Response):
@@ -35,26 +47,50 @@ def render(self, content: typing.Any) -> bytes:
3547

3648
@app.get("/v1/info/{app_id}", response_class=PrettyJSONResponse)
3749
def read_app(app_id: int, pretty: bool = False):
50+
logging.info("Requested app info", extra={"app_id": app_id})
51+
3852
if "CACHE" in os.environ and os.environ["CACHE"]:
3953
info = cache_read(app_id)
4054

4155
if not info:
42-
print("App info: " + str(app_id) + " could not be find in the cache")
56+
logging.info(
57+
"App info could not be found in cache", extra={"app_id": app_id}
58+
)
4359
info = app_info(app_id)
4460
cache_write(app_id, info)
61+
else:
62+
logging.info(
63+
"App info succesfully retrieved from cache",
64+
extra={"app_id": app_id},
65+
)
4566

4667
else:
4768
info = app_info(app_id)
4869

70+
if info is None:
71+
logging.info(
72+
"The SteamCMD backend returned no actual data and failed",
73+
extra={"app_id": app_id},
74+
)
75+
# return empty result for not found app
76+
return {"data": {app_id: {}}, "status": "failed", "pretty": pretty}
77+
4978
if not info["apps"]:
79+
logging.info(
80+
"No app has been found at Steam but the request was succesfull",
81+
extra={"app_id": app_id},
82+
)
5083
# return empty result for not found app
5184
return {"data": {app_id: {}}, "status": "success", "pretty": pretty}
5285

86+
logging.info("Succesfully retrieved app info", extra={"app_id": app_id})
5387
return {"data": info["apps"], "status": "success", "pretty": pretty}
5488

5589

5690
@app.get("/v1/version", response_class=PrettyJSONResponse)
5791
def read_item(pretty: bool = False):
92+
logging.info("Requested api version")
93+
5894
# check if version succesfully read and parsed
5995
if "VERSION" in os.environ and os.environ["VERSION"]:
6096
return {
@@ -63,6 +99,9 @@ def read_item(pretty: bool = False):
6399
"pretty": pretty,
64100
}
65101
else:
102+
logging.warning(
103+
"No version has been defined and could therefor not satisfy the request"
104+
)
66105
return {
67106
"status": "error",
68107
"data": "Something went wrong while retrieving and parsing the current API version. Please try again later",

0 commit comments

Comments
 (0)