fpdebugcapturesvc.pp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 2023 by the Free Pascal development team
  4. Class to collect debug output from a pas2js application.
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. {$IFNDEF FPC_DOTTEDUNITS}
  12. unit fpdebugcapturesvc;
  13. {$ENDIF}
  14. {$mode ObjFPC}{$H+}
  15. interface
  16. uses
  17. {$IFDEF FPC_DOTTEDUNITS}
  18. System.Classes, System.SysUtils, FpWeb.Http.Defs, FpWeb.Http.Base, FpJson.Data;
  19. {$ELSE}
  20. Classes, SysUtils, httpdefs, fphttp, fpjson;
  21. {$ENDIF}
  22. Type
  23. TDebugCaptureHandler = Procedure (aSender : TObject; aCapture : TJSONData) of object;
  24. TDebugCaptureLogHandler = Procedure (EventType : TEventType; const Msg : String) of object;
  25. { THandlerRegistrationItem }
  26. THandlerRegistrationItem = Class(TCollectionItem)
  27. private
  28. FHandler: TDebugCaptureHandler;
  29. FName: String;
  30. Public
  31. Property Name : String Read FName Write FName;
  32. Property Handler : TDebugCaptureHandler Read FHandler Write FHandler;
  33. end;
  34. { THandlerRegistrationList }
  35. THandlerRegistrationList = class(TOwnedCollection)
  36. private
  37. function GetH(aIndex : Integer): THandlerRegistrationItem;
  38. procedure SetH(aIndex : Integer; AValue: THandlerRegistrationItem);
  39. Public
  40. Function IndexOf(const aName : string) : Integer;
  41. Function Find(const aName : string) : THandlerRegistrationItem;
  42. Function Add(const aName : string; aHandler : TDebugCaptureHandler) : THandlerRegistrationItem;
  43. Property Handlers[aIndex :Integer] : THandlerRegistrationItem Read GetH Write SetH; default;
  44. end;
  45. { TDebugCaptureService }
  46. TDebugCaptureService = class(TComponent)
  47. Private
  48. class var _instance : TDebugCaptureService;
  49. private
  50. FCaptureToErrorLog: Boolean;
  51. FCors: TCORSSupport;
  52. FFileName: string;
  53. FHandlers: THandlerRegistrationList;
  54. FLogToConsole: Boolean;
  55. FOnLog: TDebugCaptureLogHandler;
  56. FCaptureStream : TStream;
  57. procedure SetCaptureToErrorLog(AValue: Boolean);
  58. procedure SetCors(AValue: TCORSSupport);
  59. procedure SetLogFileName(const AValue: string);
  60. procedure SetLogToConsole(AValue: Boolean);
  61. function GetHandlerCount: Integer;
  62. Protected
  63. Procedure DoLog(aType : TEventType; const aMsg : String);
  64. Procedure DoLog(aType : TEventType; const aFmt : String; args : Array of const);
  65. function GetCaptureJSON(ARequest: TRequest; AResponse: TResponse; var aJSON: TJSONData): TJSONArray;
  66. procedure DistributeCaptureOutput(aJSON: TJSONData); virtual;
  67. procedure DoLogToConsole(aSender: TObject; aCapture: TJSONData); virtual;
  68. procedure DoLogToErrorLog(aSender: TObject; aCapture: TJSONData); virtual;
  69. procedure DoLogToFile(aSender: TObject; aCapture: TJSONData); virtual;
  70. Function CreateRegistrationList : THandlerRegistrationList; virtual;
  71. Property Handlers : THandlerRegistrationList Read FHandlers;
  72. Public
  73. Constructor Create(aOwner:TComponent); override;
  74. Destructor Destroy; Override;
  75. class constructor init;
  76. class destructor done;
  77. class Property Instance : TDebugCaptureService Read _Instance;
  78. class function JSONDataToString(aJSON: TJSONData): TJSONStringType;
  79. Procedure HandleRequest(ARequest: TRequest; AResponse: TResponse);
  80. Procedure RegisterHandler(const aName : String; aHandler: TDebugCaptureHandler);
  81. Procedure UnregisterHandler(const aName : String);
  82. Property HandlerCount : Integer Read GetHandlerCount;
  83. Property LogFileName : string Read FFileName Write SetLogFileName;
  84. Property LogToConsole : Boolean Read FLogToConsole Write SetLogToConsole;
  85. Property CaptureToErrorLog : Boolean Read FCaptureToErrorLog Write SetCaptureToErrorLog;
  86. Property OnLog : TDebugCaptureLogHandler Read FOnLog Write FOnLog;
  87. Property CORS : TCORSSupport Read FCors Write SetCors;
  88. end;
  89. implementation
  90. { THandlerRegistrationList }
  91. function THandlerRegistrationList.GetH(aIndex : Integer): THandlerRegistrationItem;
  92. begin
  93. Result:=Items[aIndex] as THandlerRegistrationItem;
  94. end;
  95. procedure THandlerRegistrationList.SetH(aIndex : Integer; AValue: THandlerRegistrationItem);
  96. begin
  97. Items[aIndex]:=aValue;
  98. end;
  99. function THandlerRegistrationList.IndexOf(const aName: string): Integer;
  100. begin
  101. Result:=Count-1;
  102. While (Result>=0) and Not SameText(GetH(Result).Name,aName) do
  103. Dec(Result);
  104. end;
  105. function THandlerRegistrationList.Find(const aName: string): THandlerRegistrationItem;
  106. var
  107. Idx : integer;
  108. begin
  109. Result:=Nil;
  110. Idx:=IndexOf(aName);
  111. If Idx<>-1 then
  112. Result:=GetH(Idx);
  113. end;
  114. function THandlerRegistrationList.Add(const aName: string; aHandler: TDebugCaptureHandler): THandlerRegistrationItem;
  115. begin
  116. Result:=(Inherited Add) as THandlerRegistrationItem;
  117. Result.Name:=aName;
  118. Result.Handler:=aHandler;
  119. end;
  120. function TDebugCaptureService.GetCaptureJSON(ARequest: TRequest; AResponse: TResponse; var aJSON: TJSONData): TJSONArray;
  121. var
  122. aJSONObj : TJSONObject absolute aJSON;
  123. Cont : String;
  124. begin
  125. Result:=Nil;
  126. aJSON:=Nil;
  127. try
  128. Cont:=aRequest.Content;
  129. aJSON:=GetJSON(Cont);
  130. if aJSON.JSONType<>jtObject then
  131. Raise EHTTP.Create('No JSON object in capture JSON');
  132. Result:=aJSONObj.Get('lines',TJSONArray(Nil));
  133. if Result=Nil then
  134. begin
  135. FreeAndNil(aJSON);
  136. Raise EHTTP.Create('No lines element in capture JSON');
  137. end;
  138. except
  139. On E : Exception do
  140. begin
  141. DoLog(etError,Format('Exception %s (%s) : Invalid capture content: not valid JSON: %s',[E.ClassName,E.Message,Copy(Cont,1,255)]));
  142. aResponse.Code:=400;
  143. aResponse.CodeText:='INVALID PARAM';
  144. aResponse.SendResponse;
  145. end;
  146. end;
  147. end;
  148. procedure TDebugCaptureService.DoLogToErrorLog(aSender: TObject; aCapture: TJSONData);
  149. var
  150. S : TJSONStringType;
  151. begin
  152. S:=JSonDataToString(aCapture);
  153. DoLog(etInfo,'Capture : '+S);
  154. end;
  155. procedure TDebugCaptureService.DoLogToConsole(aSender: TObject; aCapture: TJSONData);
  156. var
  157. S : TJSONStringType;
  158. begin
  159. S:=JSonDataToString(aCapture);
  160. Try
  161. Writeln('Debug capture: ',S);
  162. except
  163. On E : Exception Do
  164. DoLog(etError,'Exception %s writing to console: %s',[E.ClassName,E.Message]);
  165. end;
  166. end;
  167. procedure TDebugCaptureService.DoLogToFile(aSender: TObject; aCapture: TJSONData);
  168. var
  169. S : TJSONStringType;
  170. begin
  171. S:=JSonDataToString(aCapture)+sLineBreak;
  172. if Assigned(FCaptureStream) then
  173. Try
  174. FCaptureStream.WriteBuffer(S[1],Length(S)*SizeOf(TJSONCharType));
  175. except
  176. On E : Exception Do
  177. DoLog(etError,'Exception %s writing to file %s: %s',[E.ClassName,LogFileName,E.Message]);
  178. end;
  179. end;
  180. function TDebugCaptureService.GetHandlerCount: Integer;
  181. begin
  182. Result:=FHandlers.Count;
  183. end;
  184. Const
  185. cCaptureToErrorLog = '$ErrorLog';
  186. cCaptureToFile = '$File';
  187. cCaptureToConsole = '$Console';
  188. procedure TDebugCaptureService.SetCaptureToErrorLog(AValue: Boolean);
  189. begin
  190. if FCaptureToErrorLog=AValue then Exit;
  191. FCaptureToErrorLog:=AValue;
  192. if FCaptureToErrorLog then
  193. RegisterHandler(cCaptureToErrorLog,@DoLogToErrorLog)
  194. else
  195. UnRegisterHandler(cCaptureToErrorLog);
  196. end;
  197. procedure TDebugCaptureService.SetCors(AValue: TCORSSupport);
  198. begin
  199. if FCors=AValue then Exit;
  200. FCors.Assign(AValue);
  201. end;
  202. procedure TDebugCaptureService.SetLogFileName(const AValue: string);
  203. begin
  204. if FFileName=AValue then Exit;
  205. if Assigned(FCaptureStream) then
  206. FreeAndNil(FCaptureStream);
  207. FFileName:=AValue;
  208. if FFileName<>'' then
  209. begin
  210. FCaptureStream:=TFileStream.Create(FFileName,fmCreate or fmShareDenyWrite);
  211. RegisterHandler(cCaptureToFile,@DoLogToFile)
  212. end
  213. else
  214. UnRegisterHandler(cCaptureToFile);
  215. end;
  216. procedure TDebugCaptureService.SetLogToConsole(AValue: Boolean);
  217. begin
  218. if FLogToConsole=AValue then Exit;
  219. FLogToConsole:=AValue;
  220. if FLogToConsole then
  221. RegisterHandler(cCaptureToFile,@DoLogToConsole)
  222. else
  223. UnRegisterHandler(cCaptureToFile);
  224. end;
  225. procedure TDebugCaptureService.DoLog(aType: TEventType; const aMsg: String);
  226. begin
  227. if Assigned(FOnLog) then
  228. FOnLog(aType,aMsg);
  229. end;
  230. procedure TDebugCaptureService.DoLog(aType: TEventType; const aFmt: String; args: array of const);
  231. begin
  232. if Assigned(FonLog) then
  233. {$IF DECLARED(SafeFormat)}
  234. FonLog(aType,SafeFormat(aFmt,args));
  235. {$ELSE}
  236. FonLog(aType,Format(aFmt,args));
  237. {$ENDIF}
  238. end;
  239. function TDebugCaptureService.CreateRegistrationList: THandlerRegistrationList;
  240. begin
  241. Result:=THandlerRegistrationList.Create(Self,THandlerRegistrationItem);
  242. end;
  243. constructor TDebugCaptureService.Create(aOwner: TComponent);
  244. begin
  245. inherited Create(aOwner);
  246. FHandlers:=CreateRegistrationList;
  247. FCors:=TCORSSupport.Create;
  248. end;
  249. destructor TDebugCaptureService.Destroy;
  250. begin
  251. FreeAndNil(FCors);
  252. FreeAndNil(FHandlers);
  253. inherited Destroy;
  254. end;
  255. procedure TDebugCaptureService.DistributeCaptureOutput(aJSON : TJSONData);
  256. var
  257. I : Integer;
  258. H : THandlerRegistrationItem;
  259. begin
  260. For I:=0 to FHandlers.Count-1 do
  261. Try
  262. H:=FHandlers[i];
  263. H.Handler(Self,aJSON);
  264. except
  265. On E : Exception do
  266. DoLog(etError,'Handler %s raised exception %s while handling debug capture: %s',[H.Name,E.ClassName,E.Message]);
  267. end;
  268. end;
  269. procedure TDebugCaptureService.HandleRequest(ARequest: TRequest; AResponse: TResponse);
  270. Var
  271. aJSON : TJSONData;
  272. aArray : TJSONArray;
  273. I : Integer;
  274. begin
  275. if CORS.HandleRequest(aRequest,aResponse,[hcDetect,hcSend]) then
  276. exit;
  277. aJSON:=Nil;
  278. aArray:=Nil;
  279. try
  280. aArray:=GetCaptureJSON(aRequest,aResponse,aJSON);
  281. if aArray<>Nil then
  282. begin
  283. For I:=0 to aArray.Count-1 do
  284. DistributeCaptureOutput(aArray[i]);
  285. aResponse.Code:=200;
  286. aResponse.CodeText:='OK';
  287. aResponse.SendResponse;
  288. end;
  289. finally
  290. aJSON.Free;
  291. end;
  292. end;
  293. procedure TDebugCaptureService.RegisterHandler(const aName: String; aHandler: TDebugCaptureHandler);
  294. begin
  295. If FHandlers.IndexOf(aName)<>-1 then
  296. Raise EListError.CreateFmt('Duplicate name: %s',[aName]);
  297. FHandlers.Add(aName,aHandler);
  298. end;
  299. procedure TDebugCaptureService.UnregisterHandler(const aName: String);
  300. var
  301. Idx : integer;
  302. begin
  303. Idx:=FHandlers.IndexOf(aName);
  304. if Idx<>-1 then
  305. FHandlers.Delete(Idx);
  306. end;
  307. class function TDebugCaptureService.JSONDataToString(aJSON : TJSONData): TJSONStringType;
  308. begin
  309. if aJSON.JSONType in StructuredJSONTypes then
  310. Result:=aJSON.AsJSON
  311. else if aJSON.JSONType<>jtNull then
  312. Result:=aJSON.AsString
  313. else
  314. Result:='null';
  315. end;
  316. class constructor TDebugCaptureService.init;
  317. begin
  318. _instance:=TDebugCaptureService.Create(Nil);
  319. end;
  320. class destructor TDebugCaptureService.done;
  321. begin
  322. FreeAndNil(_instance);
  323. end;
  324. end.