44import werkzeug
55import numpy as np
66import pandas as pd
7+ import logging
8+ import time
9+ from datetime import datetime
10+ from virtual_fly_brain .services .numpy_encoder import NumpyEncoder
711vfb = None
812try :
913 import vfbquery as vfb
1317from virtual_fly_brain .services .queries import run_query
1418from virtual_fly_brain .services .term_info import get_term_info
1519
16- class NumpyEncoder (json .JSONEncoder ):
17- """ Custom encoder for numpy data types """
18- def default (self , obj ):
19- if isinstance (obj , (np .int_ , np .intc , np .intp , np .int8 ,
20- np .int16 , np .int32 , np .int64 , np .uint8 ,
21- np .uint16 , np .uint32 , np .uint64 )):
2220
23- return int (obj )
24-
25- elif isinstance (obj , (np .float_ , np .float16 , np .float32 , np .float64 )):
26- return float (obj )
27-
28- elif isinstance (obj , (np .complex_ , np .complex64 , np .complex128 )):
29- return {'real' : obj .real , 'imag' : obj .imag }
30-
31- elif isinstance (obj , (np .ndarray ,)):
32- return obj .tolist ()
33-
34- elif isinstance (obj , (np .bool_ )):
35- return bool (obj )
36-
37- elif isinstance (obj , (np .void )):
38- return None
39-
40- return json .JSONEncoder .default (self , obj )
21+ # Configure logging
22+ logging .basicConfig (
23+ level = logging .INFO ,
24+ format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' ,
25+ handlers = [
26+ logging .StreamHandler () # This ensures logs go to stdout for pod logs
27+ ]
28+ )
29+
30+ # Create logger for API requests
31+ api_logger = logging .getLogger ('vfb_api' )
32+
33+
34+ def log_request (func ):
35+ """Decorator to log REST endpoint requests with input data"""
36+ def wrapper (* args , ** kwargs ):
37+ start_time = time .time ()
38+ request_id = f"{ datetime .now ().isoformat ()} _{ int (time .time () * 1000000 ) % 1000000 } "
39+
40+ # Log request start
41+ request_data = {
42+ 'request_id' : request_id ,
43+ 'endpoint' : flask .request .endpoint ,
44+ 'method' : flask .request .method ,
45+ 'url' : flask .request .url ,
46+ 'path' : flask .request .path ,
47+ 'remote_addr' : flask .request .remote_addr ,
48+ 'user_agent' : flask .request .headers .get ('User-Agent' , '' ),
49+ 'args' : dict (flask .request .args ),
50+ 'form_data' : dict (flask .request .form ) if flask .request .form else {},
51+ 'json_data' : flask .request .get_json (silent = True ) if flask .request .is_json else None ,
52+ 'content_length' : flask .request .content_length
53+ }
54+
55+ api_logger .info (f"REQUEST_START: { json .dumps (request_data , cls = NumpyEncoder )} " )
56+
57+ try :
58+ # Execute the actual endpoint function
59+ result = func (* args , ** kwargs )
60+
61+ # Log successful response
62+ end_time = time .time ()
63+ duration_ms = round ((end_time - start_time ) * 1000 , 2 )
64+
65+ response_data = {
66+ 'request_id' : request_id ,
67+ 'status' : 'SUCCESS' ,
68+ 'duration_ms' : duration_ms ,
69+ 'response_status' : getattr (result , 'status_code' , 200 ) if hasattr (result , 'status_code' ) else 200
70+ }
71+
72+ api_logger .info (f"REQUEST_END: { json .dumps (response_data , cls = NumpyEncoder )} " )
73+ return result
74+
75+ except Exception as e :
76+ # Log error response
77+ end_time = time .time ()
78+ duration_ms = round ((end_time - start_time ) * 1000 , 2 )
79+
80+ error_data = {
81+ 'request_id' : request_id ,
82+ 'status' : 'ERROR' ,
83+ 'duration_ms' : duration_ms ,
84+ 'error_type' : type (e ).__name__ ,
85+ 'error_message' : str (e )
86+ }
87+
88+ api_logger .error (f"REQUEST_ERROR: { json .dumps (error_data , cls = NumpyEncoder )} " )
89+ raise
90+
91+ wrapper .__name__ = func .__name__
92+ return wrapper
4193
4294
4395def init_webapp_routes (app ):
4496 @app .route ('/' , methods = ['GET' ])
97+ # @log_request
4598 def index ():
4699 return flask .send_from_directory ("www" , 'index.html' )
47100
48101
49102 @app .route ('/get_instances' , methods = ['GET' ])
50103 @cross_origin (supports_credentials = True )
104+ # @log_request
51105 def instances ():
52106 short_form = flask .request .args .get ('short_form' )
53107 if not short_form :
@@ -66,6 +120,7 @@ def instances():
66120
67121 @app .route ('/get_term_info' , methods = ['GET' ])
68122 @cross_origin (supports_credentials = True )
123+ # @log_request
69124 def term_info ():
70125 term_id = flask .request .args .get ('id' )
71126 if not term_id :
@@ -82,6 +137,7 @@ def term_info():
82137
83138 @app .route ('/run_query' , methods = ['GET' ])
84139 @cross_origin (supports_credentials = True )
140+ # @log_request
85141 def get_query_results ():
86142 term_id = flask .request .args .get ('id' )
87143 query_type = flask .request .args .get ('query_type' )
@@ -120,10 +176,11 @@ def handle_bad_request(e):
120176 )
121177CORS (app , support_credentials = True )
122178init_webapp_routes (app )
123- # TODO: fix this to use the init_flask function from cloudharness
124- # app = init_flask(title="Virtual Fly Brain REST API", webapp=True, init_app_fn=init_webapp_routes)
179+
125180
126181def main ():
182+ api_logger .info ("Starting Virtual Fly Brain REST API server on host='0.0.0.0', port=8080" )
183+ api_logger .info ("Logging is configured for API request tracking" )
127184 app .run (host = '0.0.0.0' , port = 8080 )
128185
129186if __name__ == '__main__' :
0 commit comments