mORMot and Open Source friends
Check-in [881797779c]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
SHA1:881797779ca2fa664348c04ff8bb313002c35708
Date: 2014-11-23 16:55:19
User: ab
Comment:{559} BREAKING CHANGE: new SynLog.pas and SynTests.pas units, extracted from SynCommons
Tags And Properties
Context
2014-11-23
18:06
[07372546f3] {560} small documentation fix (user: ab, tags: trunk)
16:55
[881797779c] {559} BREAKING CHANGE: new SynLog.pas and SynTests.pas units, extracted from SynCommons (user: ab, tags: trunk)
2014-11-22
18:01
[20cafb695f] {558} let SynDBRemote support concurrent transactions (via a simple blocking mode) (user: ab, tags: trunk)
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ReadMe.txt.

208
209
210
211
212
213
214

215
216
217
218
219
220
221
222
223
224
225
226
227
   by some enhanced features, therefore a direct update is not possible

2) Erase or rename your whole previous #\Lib directory

3) Download latest 1.18 revision files as stated just above
  
4) Change your references to mORMot units:

 - Rename in your uses clause any SQLite3Commons reference into mORmot;
 - Rename in your uses clause any SQLite3 reference into mORMotSQLite3;
 - Rename in your uses clause any other SQlite3* reference into mORMot*;
 - Add in one uses clause a link to SynSQLite3Static (for Win32).
 
5) Consult the units headers about 1.18 for breaking changes, mainly:
 - TSQLRecord.ID: TID primary key, TIDDynArray, and TRecordReference are now Int64;
 - Renamed Iso8601 low-level structure as TTimeLogBits;
 - TJSONSerializerCustomReader/Writer callbacks changed;
 - TSQLRestServerCallBackParams replaced by TSQLRestServerURIContext class;
 - TSQLRestServerStatic* classes renamed as TSQLRestStorage*;
 - rmJSON* enums replaced by TSQLRestRoutingREST/JSON_RPC classes;
 - Changed '¤' into '~' character for mORMoti18n language files.







>













208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
   by some enhanced features, therefore a direct update is not possible

2) Erase or rename your whole previous #\Lib directory

3) Download latest 1.18 revision files as stated just above
  
4) Change your references to mORMot units:
 - Add in your uses clause SynLog.pas and/or SynTests.pas if needed;
 - Rename in your uses clause any SQLite3Commons reference into mORmot;
 - Rename in your uses clause any SQLite3 reference into mORMotSQLite3;
 - Rename in your uses clause any other SQlite3* reference into mORMot*;
 - Add in one uses clause a link to SynSQLite3Static (for Win32).
 
5) Consult the units headers about 1.18 for breaking changes, mainly:
 - TSQLRecord.ID: TID primary key, TIDDynArray, and TRecordReference are now Int64;
 - Renamed Iso8601 low-level structure as TTimeLogBits;
 - TJSONSerializerCustomReader/Writer callbacks changed;
 - TSQLRestServerCallBackParams replaced by TSQLRestServerURIContext class;
 - TSQLRestServerStatic* classes renamed as TSQLRestStorage*;
 - rmJSON* enums replaced by TSQLRestRoutingREST/JSON_RPC classes;
 - Changed '¤' into '~' character for mORMoti18n language files.

Changes to SQLite3/Documentation/Synopse SQLite3 Framework.pro.

738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
....
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273


1274
1275
1276
1277
1278
1279
1280
1281
1282
1283

1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
....
1295
1296
1297
1298
1299
1300
1301

1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
....
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
....
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
....
4972
4973
4974
4975
4976
4977
4978
4979
4980
4981
4982
4983
4984
4985
4986
....
7693
7694
7695
7696
7697
7698
7699
7700
7701
7702
7703
7704
7705
7706
7707
....
7733
7734
7735
7736
7737
7738
7739
7740
7741
7742
7743
7744
7745
7746
7747
....
9963
9964
9965
9966
9967
9968
9969
9970
9971
9972
9973
9974
9975
9976
9977
.....
10109
10110
10111
10112
10113
10114
10115
10116
10117
10118
10119
10120
10121
10122
10123
.....
12488
12489
12490
12491
12492
12493
12494


12495
12496
12497
12498
12499
12500
12501
.....
12502
12503
12504
12505
12506
12507
12508
12509
12510
12511
12512
12513
12514
12515
12516
.....
12608
12609
12610
12611
12612
12613
12614
12615
12616
12617
12618
12619
12620
12621
12622
12623


12624
12625
12626




















12627























































































































































































































































































12628
12629
12630
12631
12632
12633
12634
12635
.....
12686
12687
12688
12689
12690
12691
12692
12693
12694
12695
12696
12697
12698
12699
12700
.....
12741
12742
12743
12744
12745
12746
12747

12748
12749
12750
12751
12752
12753
12754
.....
12755
12756
12757
12758
12759
12760
12761

12762
12763
12764
12765
12766
12767
12768
.....
12771
12772
12773
12774
12775
12776
12777

12778
12779
12780
12781
12782
12783
12784
.....
12850
12851
12852
12853
12854
12855
12856


12857
12858
12859
12860
12861
12862
12863
- If you are still using an old version of {\i Delphi}, and can't easily move up due to some third party components or existing code base, {\i mORMot} will offer all the needed features to start ORM, N-Tier and SOA, starting with a {\i Delphi} 6 edition.
{\i mORMot} implements the needed techniques for introducing what Michael Feathers calls, in his book {\i Working Effectively With Legacy Code}, a @**seam@. A seam is an area where you can start to cleave off some legacy code and begin to introduce changes. Even mocking abilities of {\i mORMot} - see @62@ - will help you in this delicate task - see @http://www.infoq.com/articles/Utilizing-Logging
Do not forget that {\i Synopse}, as a company, is able to offer dedicated audit and support for such a migration. The sooner, the better.
\page
:123 FAQ
Before you start going any further, we propose here below a simple @**FAQ@ containing the most frequent questions we received on our forums.
First of all, take a look at the {\i keyword index} available at the very beginning of this document. The underlined entries targets the main article about a given concept or technical term.
Feel free to give your feedback in the very same forum, asking new questions or improving answers!
{\b Your SAD doc is too long to read through in a short period.}\line Too much documentation can kill the documentation! But you do not need to read the whole document: most of it is a detailed description of every unit, object, or class. But the first part is worth reading, otherwise you are very likely to miss some main concepts or patterns. It just takes 15-30 minutes!
{\b Where should I start?}\line Take a look at the {\i Architecture principles} @40@, then download and install the sources and compile and run the {\f1\fs20 TestSQL3.dpr} program as stated @113@. Check about @*ORM@ @3@, @*SOA@ @63@ and @*MVC@ @108@, then test the various samples (from the {\f1\fs20 SQLite3\\Samples} folder), especially 01, 02, 04, 11, 12, 14, 17, 26, 28, 30 and the {\f1\fs20 MainDemo}.
{\b So far, I can see your {\i mORMot} fits most of the requirement, but seems only for Database Client-Server apps.}\line First of all, the framework is a {\i set of bricks}, so you can use it e.g. to build interface based services, even with no database at all. We tried to make its main features modular and uncoupled.
{\b I am not a great fan of ORM, sorry, I still like SQL and have some experience of that. Some times sophisticated SQL query is hard to change to ORM code.}\line ORM can make development much easier; but you can use e.g. interface-based services and "manual" SQL statements - in this case, you have at hand @27@ classes in {\i mORMot}, which allow very high performance and direct export to JSON.
{\b I am tempted by using an ORM, but {\i mORMot} forces you to inherit from a root {\f1\fs20 @*TSQLRecord@} type, whereas I'd like to use any kind of object.}\line Adding attributes to an existing class is tempting, but would pollute your code at the end, mixing persistence and business logic: see {\i @*Persistence Ignorance@} and {\i @*Aggregates@} @124@. Our ORM does not rely on {\i generics}, but on the power of the object pascal {\f1\fs20 class} used with {\i @*convention over configuration@} - so our code is faster, and works with older versions of Delphi, or FreePascal.
{\b I also notice in your SAD doc, data types are different from Delphi. You have {\f1\fs20 RawUTF8}, etc, which make me puzzled, what are they?}\line You can use standard {\i Delphi} types, but some more optimized types were defined: since the whole framework is @*UTF-8@ based, we defined a dedicated type, which works with all versions of {\i Delphi}, before and after {\i Delphi} 2009. By the way, just search for {\f1\fs20 RawUTF8} in the {\i keyword index} of this document.
{\b All the objects seem non-VCL components, meaning need code each property and remember them all well.}\line This is indeed... a feature. The framework is not @*RAD@, but fully object-oriented. Thanks to the {\i Delphi} IDE, you can access all properties description via auto-completion and/or code navigation. Then you can still use RAD for UI design, but let business be abstracted in pure code.
{\b I know you have joined the {\i DataSnap} performance discussion and your performance won good reputation there. If I want to use your framework to replace my old project of DataSnap, how easy will it be?}\line If you used {\i DataSnap} to build method-based services, translation into {\i mORMot} would be just a matter of code refactoring. And you will benefit of new features like {\i Interface-based services} - see @63@ - which is much more advanced than the method-based pattern, and will avoid generating the client class via a wizard, and offers additional features - see @77@ or @72@. If you used {\i DataSnap} to access a remote database and linked to VCL components, see the {\f1\fs20 mORMotVCL.pas} unit which can publish a data source for your UI.
{\b What is the SMS? Do you know any advantage compared to JQuery?}\line {\i @*Smart Mobile Studio@} is an IDE and some source runtime able to develop and compile an Object-Pascal project into a {\i @*HTML 5@ / @*CSS 3@ / @*JavaScript@} {\i embedded} application, i.e. able to work stand alone with no remote server. When used with {\i mORMot} on the server side, you can use the very same object pascal language on both server and client sides, with strong typing and true @*OOP@ design. Then you feature secure authentication and JSON communication, with connected or off-line mode. Your {\i SmartPascal} client code can be generated by your {\i mORMot} server, as stated @90@.
{\b I am trying to search a substitute solution to WebSnap. Do you have any sample or doc to describe how to build a robust web Server?}\line You can indeed easily create a modern @*MVC@ / @*MVVM@ scaling @*Web Application@. Your {\i mORMot} server can easily publish its ORM / SOA business logic as {\i Model}, use {\i @*Mustache@} logic-less templates rendering - see @81@ - for {\i Views}, and defining the {\i ViewModel} / {\i Controller} as regular Delphi methods. See @108@ for more details, and discovering a sample "blog" application.
{\b Have you considered using a popular source coding host like @*Github@ or BitBucket?}\line We love to host our own source code repository, and find fossil a perfect match for our needs, with a friendly approach. But we created a parallel repository on {\i GitHub}, so that you may be able to monitor or fork our projects - see @http://github.com/synopse/mORMot \line Note that you can get a daily snapshot of our official source code repository directly from\line @http://synopse.info/files/mORMotNightlyBuild.zip
{\b Why is this framework named {\i mORMot}?}\line - Because its initial identifier was "{\i Synopse SQLite3 database framework}", which may induce a {\i SQLite3}-only library, whereas the framework is now able to connect to any database engine;\line - Because we like mountains, and those large ground rodents;\line - Because marmots do hibernate, just like our precious objects;\line - Because marmots are highly social and use loud whistles to communicate with one another, just like our applications are designed not to be isolated;\line - Because even if they eat greens, they use to fight at Spring for their realm;\line - Because it may be an acronym for "Manage Object Relational Mapping Over Territory", or whatever you may think of...
\page
................................................................................
;In the following sections, the {\i Synopse mORMot Framework} library architecture is detailed as:
;\SourcePage

[SAD-Main]
SourcePath=Lib\SQLite3
IncludePath=Lib;Lib\SQLite3;Lib\SynDBDataset;Lib\CrossPlatform;Zeos\src
;Lib\SQLite3\Samples\MainDemo
SourceFile=TestSQL3.dpr;mORMot.pas;mORMoti18n.pas;mORMotToolBar.pas;mORMotUI.pas;mORMotUIEdit.pas;mORMotMVC.pas;mORMotUILogin.pas;mORMotReport.pas;mORMotUIOptions.pas;mORMotUIQuery.pas;mORMotService.pas;mORMotSQLite3.pas;mORMotHttpClient.pas;mORMotHttpServer.pas;SynSQLite3.pas;SynSQLite3Static.pas;SynSQLite3RegEx.pas;SynDB.pas;SynOleDB.pas;SynDBOracle.pas;SynDBSQLite3.pas;SynDBODBC.pas;SynDBDataset.pas;SynDBZeos.pas;SynDBFireDAC.pas;SynDBUniDAC.pas;SynDBBDE.pas;SynDBNexusDB.pas;SynDBVCL.pas;mORMotReport.pas;mORMotVCL.pas;mORMotDB.pas;mORMotFastCGIServer.pas;SynSM.pas;SynDBMidasVCL.pas;mORMotMidasVCL.pas;SynMongoDB.pas;SynCrossPlatformJSON.pas;SynCrossPlatformREST.pas;SynCrossPlatformSpecific.pas;SynCrossPlatformTests.pas
;Samples\MainDemo\SynFile.dpr
Version=1.18
TitleOffset=0
DisplayName=mORMot Framework

:Enter new territory
: Meet the mORMot
The {\i Synopse mORMot} framework consists in a huge number of units, so we will start by introducing them.
\graph mORMotSourceCodeMainUnits mORMot Source Code Main Units
node [shape="box"];
rankdir=LR;
\mORMot.pas\SynCommons.pas


\mORMotDB.pas\mORMot.pas
\mORMotDB.pas\SynCommons.pas
\mORMotDB.pas\SynDB.pas
\UI\mORMot.pas
\UI\SynCommons.pas
\mORMotSQlite3.pas\SynCommons.pas
\mORMotSQlite3.pas\mORMot.pas
\mORMotSQlite3.pas\SynSQLite3.pas¤SynSQLite3Static.pas
\SynSQLite3.pas¤SynSQLite3Static.pas\SynCommons.pas
\SynDB.pas\SynCommons.pas

\SynDBSQlite3.pas\SynDB.pas
\SynDBSQlite3.pas\SynCommons.pas
\SynDBSQlite3.pas\SynSQLite3.pas¤SynSQLite3Static.pas
\SynDBOracle.pas¤SynDBODBC.pas¤SynOleDB.pas¤SynDBZEOS.pas¤SynDBDataset.pas¤SynDBFireDAC.pas¤SynDBUniDAC.pas¤SynDBNexusDB.pas¤SynDBBDE.pas\SynDB.pas
\mORMotDB.pas\SynSQLite3.pas¤SynSQLite3Static.pas
\mORMotReport.pas\SynCommons.pas
\mORMotReport.pas\SynPdf.pas
\SynPdf.pas\SynCommons.pas
\SynSMAPI.pas¤SynSM.pas\SynCommons.pas
\HTTP\SynCrtSock.pas
\HTTP\mORMot.pas
................................................................................
\HTTP\SynCommons.pas
\mORMotMongoDB.pas\SynMongoDB.pas
\mORMotMongoDB.pas\mORMot.pas
\mORMotMVC.pas\mORMot.pas
\mORMotMVC.pas\SynCommons.pas
\mORMotMVC.pas\SynMustache.pas
\SynMongoDB.pas\SynCommons.pas

\SynMongoDB.pas\SynCrtSock.pas
=UI=mORMotUI.pas¤mORMotToolBar.pas¤mORMoti18n.pas¤mORMotUILogin.pas¤mORMotUIEdit.pas¤mORMotUIQuery.pas¤mORMotUIOptions.pas
=HTTP=mORMotHttpClient.pas¤mORMotHttpServer.pas
\mORMotReport.pas=UI=SynSMAPI.pas¤SynSM.pas
\
;When you will finish reading this document, you won't be afraid any more! :)
\page
: Main units
The main units you have to be familiar with are the following:
|%30%70
|\b Unit name|Description\b0
|{\f1\fs20 SynCommons.pas}|Common types, classes and functions
|{\f1\fs20 mORMot.pas}|Main unit of the @*ORM@ / @*SOA@ framework
|{\f1\fs20 SynSQLite3.pas\line SynSQLite3Static.pas}|{\i @*SQLite3@} database engine
|{\f1\fs20 mORMotSQLite3.pas}|Bridge between {\f1\fs20 mORMot.pas} and {\f1\fs20 SynSQLite3.pas}
|{\f1\fs20 @*SynDB@.pas\line SynDB*.pas}|Direct RDBMS access classes
|{\f1\fs20 mORMotDB.pas}|ORM external {\f1\fs20 SynDB.pas} access, via {\i SQlite3} virtual tables
|{\f1\fs20 SynMongoDB.pas\line mORMotMongoDB.pas}|Direct access to a {\i @*MongoDB@} server
|{\f1\fs20 SynSM*.pas}|{\i SpiderMonkey} JavaScript engine
|{\f1\fs20 mORMotHttpClient.pas\line mORMotHttpServer.pas\line SynCrtSock.pas}|@*REST@ful HTTP/1.1 Client and Server
|{\f1\fs20 mORMotMVC.pas\line SynMustache.pas}|@*MVC@ classes for writing @*Web Application@s
|{\f1\fs20 mORMotUI*.pas}|Grid and Forms User Interface generation
|{\f1\fs20 mORMotToolBar.pas}|ORM ToolBar User Interface generation
|{\f1\fs20 mORMotReport.pas}|Integrated Reporting engine
|%
Other units are available in the framework source code repository, but are either expected by those files above (e.g. like {\f1\fs20 SynDB*.pas} database providers), or used only optionally in end-user @*cross-platform@ client applications (e.g. the {\f1\fs20 CrossPlatform} folder).
................................................................................
First of all, a {\f1\fs20 @*Synopse.inc@} include file is provided, and appears in most of the framework units:
!{$I Synopse.inc} // define HASINLINE USETYPEINFO CPU32 CPU64
It will define some conditionals, helping write portable and efficient code.
In the following next paragraphs, we'll comment some main features of the lowest-level part of the framework, mainly located in @!Lib\SynCommons.pas@:
- Unicode and @*UTF-8@;
- {\f1\fs20 @*Currency@} type;
- {\i @*Dynamic array@} wrappers ({\f1\fs20 TDynArray} and {\f1\fs20 TDynArrayHashed});
- {\f1\fs20 @*TDocVariant@} custom {\f1\fs20 variant} type for dynamic schema-less {\i object} or {\i array} storage;
- @*Log@ging.
:32 Unicode and UTF-8
Our {\i mORMot} Framework has 100% UNICODE compatibility, that is compilation under {\i Delphi} 2009 and up (including latest XE7 revision). The code has been deeply rewritten and @*test@ed, in order to provide compatibility with the {\f1\fs20 String=UnicodeString} paradigm of these compilers.  But the code will also handle safely Unicode for older versions, i.e. from {\i Delphi} 6 up to {\i Delphi} 2007.
Since our framework is natively @**UTF-8@ (this is the better character encoding for fast @*JSON@ streaming/parsing and it is natively supported by the {\i @*SQLite3@} engine), we had to establish a secure way our framework used strings, in order to handle all versions of {\i Delphi} (even pre-Unicode versions, especially the {\i Delphi} 7 version we like so much), and provide compatibility with the {\i @*FreePascal@ Compiler}.
Some string types have been defined, and used in the code for best cross-compiler efficiency (avoiding most conversion between formats):
- {\f1\fs20 @**RawUTF8@} is used for every internal data usage, since both {\i SQLite3} and JSON do expect UTF-8 encoding;
- {\f1\fs20 WinAnsiString} where {\i WinAnsi}-encoded {\f1\fs20 AnsiString} (code page 1252) are needed;
- Generic {\f1\fs20 string} for {\i @*i18n@} (e.g. in unit {\f1\fs20 mORMoti18n}), i.e. text ready to be used within the VCL, as either {\f1\fs20 AnsiString} (for {\i Delphi} 2 to 2007) or {\f1\fs20 UnicodeString} (for {\i Delphi} 2009 and later);
................................................................................
- Client-Server ORM - see @3@ - will support {\f1\fs20 TDocVariant} in any of the {\f1\fs20 TSQLRecord variant} published properties (and store them as @*JSON@ in a text column);
- Interface-based services - see @63@ - will support {\f1\fs20 TDocVariant} as {\f1\fs20 variant} parameters of any method, which make them as perfect {\i DTO};
- Since JSON support is implemented with any {\f1\fs20 TDocVariant} value from the ground up, it makes a perfect fit for working with AJAX clients, in a script-like approach;
- If you use our {\f1\fs20 SynMongoDB.pas mORMotMongoDB.pas} units to access a {\i @*MongoDB@} server, {\f1\fs20 TDocVariant} will be the native storage to create or access nested @*BSON@ arrays or objects documents - that is, it will allow proper @*ODM@ storage;
- Cross-cutting features (like logging or {\f1\fs20 record} / {\i dynamic array} enhancements) will also benefit from this {\f1\fs20 TDocVariant} custom type.
We are pretty convinced that when you will start playing with {\f1\fs20 TDocVariant}, you won't be able to live without it any more. It introduces the full power of @*late-binding@ and dynamic schema-less patterns to your application code, which can be pretty useful for prototyping or in Agile development. You do not need to use scripting engines like {\i Python} or {\i JavaScript}: {\i Delphi} is perfectly able to handle dynamic coding!
\page
:16 Enhanced logging
A @**log@ging mechanism is integrated with cross-cutting features of the framework. It includes stack trace exception and such, just like {\i MadExcept}, using {\f1\fs20 .map} file content to retrieve debugging information from the source code.
Here are some of its features:
- Logging with a {\i set} of levels, not only a level scale;
- Fast, low execution overhead;
- Can load {\f1\fs20 .map} file symbols to be displayed in logging (i.e. source code file name and line numbers are logged instead of a hexadecimal value);
- Compression of {\f1\fs20 .map} into binary {\f1\fs20 .mab} (900 KB -> 70 KB);
- Inclusion of the {\f1\fs20 .map/.mab} into the {\f1\fs20 .exe}, with very slow size increase;
- Exception logging ({\i Delphi} or low-level exceptions) with unit names and line numbers;
- Optional stack trace with units and line numbers;
- Methods or procedure recursive tracing, with {\i Enter} and {\i auto-Leave} (using a fake {\f1\fs20 interface} instance);
- High resolution time stamps, for customer-side profiling of the application execution;
- Set / enumerates / {\f1\fs20 TList} / {\f1\fs20 TPersistent} / {\f1\fs20 TObjectList} / dynamic array JSON serialization;
- Per-thread or global logging;
- Optional multiple log files on the same process;
- Optional rotation when main log reaches a specified size, with compression of the rotated logs;
- Integrated log archival (in {\f1\fs20 .zip} or any other format, including our {\f1\fs20 .synlz});
- Optional colored echo to a console window, for interactive debugging;
- Fast log viewer tool available, including thread filtering and customer-side execution profiling;
- Optional remote logging via HTTP - the log viewer can be used as server.
:  Setup logging
Logging is defined mainly by a per-class approach. You usually define your logging expectations by using a {\f1\fs20 TSynLog} class, and setting its {\f1\fs20 Family} property. Note that it is perfectly feasible to use you own {\f1\fs20 TSynLog} class instance, with its own {\f1\fs20 TSynLog} family settings, injected at the {\f1\fs20 constructor} level; but in {\i mORMot}, we usually use the per-class approach, via {\f1\fs20 TSynLog}, {\f1\fs20 TSQLLog}, {\f1\fs20 SynDBLog} and {\f1\fs20 SQLite3Log} - see @73@.
For sample code (and the associated log viewer tool), see "{\i 11 - Exception logging}" folder in "{\i Sqlite3\\Samples}".
In short, you can add logging to your program, just by using the {\f1\fs20 TSynLog} class, as such:
!  TSynLog.Add.Log(sllInfo,Stats.DebugMessage);
This line will log the {\f1\fs20 Stats.DebugMessage} text, with a {\f1\fs20 sllInfo} notification level. See the description of all {\f1\fs20 Log()} overloaded methods of the {\f1\fs20 ISynLog} interface, to find out how your project can easily log events.
First of all, you need to define your logging setup via code:
!  with TSynLog.Family do begin
!    Level := LOG_VERBOSE;
!    //Level := [sllException,sllExceptionOS];
!    //HighResolutionTimeStamp := true;
!    //AutoFlushTimeOut := 5;
!    OnArchive := EventArchiveSynLZ;
!    //OnArchive := EventArchiveZip;
!    ArchiveAfterDays := 1; // archive after one day
!  end;
The main setting here is {\f1\fs20 TSynLog.Family.Level := ...} which defines which levels are to be logged. That is, if {\f1\fs20 sllInfo} is part of {\f1\fs20 TSynLog.Family.Level}, any {\f1\fs20 TSynLog.Add.Log(sllInfo,...)} command will log the corresponding content - otherwise, it will be a no-operation. {\f1\fs20 LOG_VERBOSE} is a constant setting all levels at once.
You have several debugging levels available, and even 4 custom types:
!  TSynLogInfo = (
!    sllNone, sllInfo, sllDebug, sllTrace, sllWarning, sllError,
!    sllEnter, sllLeave,
!    sllLastError, sllException, sllExceptionOS, sllMemory, sllStackTrace,
!    sllFail, sllSQL, sllCache, sllResult, sllDB, sllHTTP, sllClient, sllServer,
!    sllServiceCall, sllServiceReturn, sllUserAuth,
!    sllCustom1, sllCustom2, sllCustom3, sllCustom4, sllNewRun);
Here are the purpose of each logging level:
- {\f1\fs20 sllInfo} will log general information events;
- {\f1\fs20 sllDebug} will log detailed debugging information;
- {\f1\fs20 sllTrace} will log low-level step by step debugging information;
- {\f1\fs20 sllWarning} will log unexpected values (not an error);
- {\f1\fs20 sllError} will log errors;
- {\f1\fs20 sllEnter} will log every method start;
- {\f1\fs20 sllLeave} will log every method quit;
- {\f1\fs20 sllLastError} will log the {\f1\fs20 GetLastError} OS message;
- {\f1\fs20 sllException} will log all exception raised - available since Windows XP;
- {\f1\fs20 sllExceptionOS} will log all OS low-level exceptions ({\f1\fs20 EDivByZero, ERangeError, EAccessViolation}...);
- {\f1\fs20 sllMemory} will log memory statistics;
- {\f1\fs20 sllStackTrace} will log caller's stack trace (it is by default part of {\f1\fs20 TSynLogFamily. LevelStackTrace} like {\f1\fs20 sllError, sllException, sllExceptionOS, sllLastError} and {\f1\fs20 sllFail});
- {\f1\fs20 sllFail} was defined for {\f1\fs20 TSynTestsLogged. Failed} method, and can be used to log some customer-side assertions (may be notifications, not errors);
- {\f1\fs20 sllSQL} is dedicated to trace the SQL statements;
- {\f1\fs20 sllCache} should be used to trace any internal caching mechanism (it is used for instance by our SQL statement caching);
- {\f1\fs20 sllResult} could trace the SQL results, JSON encoded;
- {\f1\fs20 sllDB} is dedicated to trace low-level database engine features;
- {\f1\fs20 sllHTTP} could be used to trace HTTP process;
- {\f1\fs20 sllClient/sllServer} could be used to trace some Client or Server process;
- {\f1\fs20 sllServiceCall/sllServiceReturn} to trace some remote service or library;
- {\f1\fs20 sllUserAuth} to trace user authentication (e.g. for individual requests);
- {\f1\fs20 sllCustom1..sllCustom4} items can be used for any purpose by your programs;
- {\f1\fs20 sllNewRun} will be written when a process re-opens a rotated log.
Logging is not using directly a {\f1\fs20 TSynLogInfo} level, but the following {\f1\fs20 set}:
!  /// used to define a logging level
!  // - i.e. a combination of none or several logging event
!  // - e.g. use LOG_VERBOSE constant to log all events
!  TSynLogInfos = set of TSynLogInfo;
Most logging tools in the wild use a level scale, i.e. with a hierarchy, excluding the lower levels when one is selected.
Our logging classes use a {\i set}, and not directly a particular level, so you are able to select which exact events are worth recording. In practice, we found this pattern to make a lot of sense and to be much more efficient for support.
:  Call trace
The logging mechanism can be used to trace recursive calls. It can use an interface-based mechanism to log when you enter and leave any method:
!procedure TMyDB.SQLExecute(const SQL: RawUTF8);
!var ILog: ISynLog;
!begin
!  ILog := TSynLogDB.Enter(self,'SQLExecute');
!  // do some stuff
!  ILog.Log(sllInfo,'SQL=%',[SQL]);
!end; // when you leave the method, it will write the corresponding event to the log
It will be logged as such:
$20110325 19325801  +    MyDBUnit.TMyDB(004E11F4).SQLExecute
$20110325 19325801 info   SQL=SELECT * FROM Table;
$20110325 19325801  -
Note that by default you have human-readable {\i time and date} written to the log, but it is also possible to replace this timing with {\i high-resolution timestamps}. With this, you'll be able to profile your application with data coming from the customer side, on its real computer. Via the {\f1\fs20 Enter} method (and its {\i auto-Leave} feature), you have all information needed for this.
:  Including symbol definitions
In the above logging content, the method name is set in the code (as {\f1\fs20 'SQLExecute'}). But if the logger class is able to find a {\f1\fs20 .map} file associated to the {\f1\fs20 .exe}, the logging mechanism is able to read this symbol information, and write the exact line number of the event.
By default, the {\f1\fs20 .map} file information is not generated by the compiler. To force its creation, you must ensure the {\f1\fs20 \{$D+\}} compiler directive is set in every unit (which is the case by default, unless you set {\f1\fs20 \{$D-\}} in the source), and the "{\i Detailed Map File}" option selected in the {\i Project > Options > Linker} page of the {\i Delphi} IDE.
In the following log entries, you'll see both high-resolution time stamp, and the entering and leaving of a {\f1\fs20 TTestCompression.TestLog} method traced with no additional code (with accurate line numbers, extracted from the {\f1\fs20 .map} content):
$0000000000000B56  +    TTestCompression(00AB3570).000E6C79 SynSelfTests.TTestCompression.TestLog (376)
$0000000000001785  -
There is already a dedicated {\f1\fs20 TSynLogFile} class able to read the {\f1\fs20 .log} file, and recognize its content.
The first time the {\f1\fs20 .map} file is read, a {\f1\fs20 .mab} file is created, and will contain all symbol information needed. You can send the {\f1\fs20 .mab} file with the {\f1\fs20 .exe} to your client, or even embed its content to the {\f1\fs20 .exe} (see the {\f1\fs20 Map2Mab.dpr} sample file located in the {\f1\fs20 Samples\\11 - Exception logging\\} folder).
This {\f1\fs20 .mab} file is very optimized: for instance, a {\f1\fs20 .map} of 927,984 bytes compresses into a 71,943 {\f1\fs20 .mab} file.
:  Exception handling
Of course, this @*log@ging mechanism is able to intercept the raise of exceptions, including the worse (e.g. {\f1\fs20 EAccessViolation}), to be logged automatically in the log file, as such:
$000000000000090B EXCOS EAccessViolation (C0000005) at 000E9C7A SynSelfTests.Proc1 (785)  stack trace 000E9D51 SynSelfTests.Proc2 (801) 000E9CC1 SynSelfTests.Proc1 (790) 000E9D51 SynSelfTests.Proc2 (801) 000E9CC1 SynSelfTests.Proc1 (790) 000E9D51 SynSelfTests.Proc2 (801) 000E9CC1 SynSelfTests.Proc1 (790) 000E9D51 SynSelfTests.Proc2 (801) 000E9CC1 SynSelfTests.Proc1 (790) 000E9D51 SynSelfTests.Proc2 (801) 000E9CC1 SynSelfTests.Proc1 (790) 000E9E2E SynSelfTests.TestsLog (818) 000EA0FB SynSelfTests (853) 00003BF4 System.InitUnits 00003C5B System.@StartExe 000064AB SysInit.@InitExe 000EA3EC TestSQL3 (153)
The {\f1\fs20 TSynLogInfo} logging level makes a difference between high-level {\i Delphi} exceptions ({\f1\fs20 sllException}) and lowest-level OS exceptions ({\f1\fs20 sllExceptionOS}) like {\f1\fs20 EAccessViolation}.
For instance, if you add to your program:
!uses
!  SynCommons;
!(...)
!  TSynLog.Family.Level := [sllExceptionOS];
all OS exceptions (excluding pure {\i Delphi} exception like {\f1\fs20 EConvertError} and such) will be logged to a separated log file.
!TSynLog.Family.Level := [sllException,sllExceptionOS];
will trace also {\i Delphi} exceptions, for instance.
You can specify some {\f1\fs20 Exception} class to be ignored, by adding them to {\f1\fs20 Family.ExceptionIgnore} internal list. It could make sense to add this setting, if your code often triggers some non-breaking exceptions, e.g. with {\f1\fs20 StrToInt()}:
!  TSynLog.Family.ExceptionIgnore.Add(EConvertError);
If your {\i Delphi} code executes some {\i .Net} managed code (e.g. exposed via some COM wrapper components), the unit is able to recognize most un-handled {\i .Net} exceptions, and log them with their original {\f1\fs20 C#} class name (for instance, {\f1\fs20 EOleSysError 80004003} will be recorded as a much more user-friendly "{\f1\fs20 [.NET/CLR unhandled ArgumentNullException]}" message.
You can set the following global variable to assign a customized callback, and be able to customize the logging content associated to any exception:
!type
!  /// global hook callback to customize exceptions logged by TSynLog
!  // - should return FALSE if Context.EAddr and Stack trace is to be appended
!  TSynLogExceptionToStr = function(WR: TTextWriter; const Context: TSynLogExceptionContext): boolean;
!
!var
!  /// allow to customize the Exception logging message
!  TSynLogExceptionToStrCustom: TSynLogExceptionToStr = nil;
The {\f1\fs20 Context: TSynLogExceptionContext} content is to be used to append some text to the specified {\f1\fs20 TTextWriter} instance.
An easier possibility is to inherit your custom exception class from {\f1\fs20 ESynException}, and override its unique virtual method:
!  /// generic parent class of all custom Exception types of this unit
!  ESynException = class(Exception)
!  public
!    /// can be used to customize how the exception is logged
!    // - this default implementation will call the DefaultSynLogExceptionToStr()
!    // callback or TSynLogExceptionToStrCustom, if defined
!    // - override this method to provide a custom logging content
!    // - should return TRUE if Context.EAddr and Stack trace is not to be
!    // written (i.e. as for any TSynLogExceptionToStr callback)
!!    function CustomLog(WR: TTextWriter; const Context: TSynLogExceptionContext): boolean; virtual;
!  end;
See {\f1\fs20 TSynLogExceptionContext} to check the execution context, and the implementation of the {\f1\fs20 function DefaultSynLogExceptionToStr()} function.
:  Serialization
{\i @*dynamic array@s} can also be serialized as @*JSON@ in the log on request, via the default {\f1\fs20 TSynLog} class, as defined in {\f1\fs20 SynCommons.pas} unit - see @48@.
The {\f1\fs20 TSQLLog} class (using the enhanced @*RTTI@ methods defined in {\f1\fs20 mORMot.pas} unit) is even able to serialize {\f1\fs20 @*TSQLRecord@, @*TPersistent@, TList} and {\f1\fs20 @*TCollection@} instances as JSON, or any other class instance, after call to {\f1\fs20 TJSONSerializer. @*RegisterCustomSerializer@}.
For instance, the following code:
!procedure TestPeopleProc;
!var People: TSQLRecordPeople;
!    Log: ISynLog;
!begin
!  Log := TSQLLog.Enter;
!  People := TSQLRecordPeople.Create;
!  try
!    People.ID := 16;
!    People.FirstName := 'Louis';
!    People.LastName := 'Croivébaton';
!    People.YearOfBirth := 1754;
!    People.YearOfDeath := 1793;
!    Log.Log(sllInfo,People);
!  finally
!    People.Free;
!  end;
!end;
will result in the following log content:
$0000000000001172  +    000E9F67 SynSelfTests.TestPeopleProc (784)
$000000000000171B info      {"TSQLRecordPeople(00AB92E0)":{"ID":16,"FirstName":"Louis","LastName":"Croivébaton","Data":"","YearOfBirth":1754,"YearOfDeath":1793}}
$0000000000001731  -
:  Multi-threaded applications
You can define several @*log@ files per process, and even a per-thread log file, if needed (it could be sometimes handy, for instance on a server running the same logic in parallel in several threads).
The logging settings are made at the logging class level. Each logging class (inheriting from {\f1\fs20 TSynLog}) has its own {\f1\fs20 TSynLogFamily} instance, which is to be used to customize the logging class level. Then you can have several instances of the individual {\f1\fs20 TSynLog} classes, each class sharing the settings of the {\f1\fs20 TSynLogFamily}.
You can therefore initialize the "family" settings before using logging, like in this code which will force to log all levels ({\f1\fs20 LOG_VERBOSE}), and create a per-thread log file, and write the {\f1\fs20 .log} content not in the {\f1\fs20 .exe} folder, but in a custom directory:
! with TSynLogDB.Family do
! begin
!   Level := LOG_VERBOSE;
!!   PerThreadLog := ptOneFilePerThread;
!   DestinationPath := 'C:\Logs';
! end;
If you specifies {\f1\fs20 PerThreadLog := ptIdentifiedInOnFile} for the family, a new column will be added for each log row, with the corresponding {\f1\fs20 ThreadID} - the supplied {\f1\fs20 LogView} tool will handle it as expected. This can be very useful for a multi-threaded server process, e.g. as implement with {\i mORMot}'s Client-Server classes @35@.
:  Log to the console
For debugging purposes, it could be very handy to output the logging content to a console window. It enables interactive debugging of a Client-Server process, for instance: you can interact with the Client, then look in real time at the server console window, and inspect which requests are processed, without the need to open the log file.
The {\f1\fs20 EchoToConsole} property enable you to select which events are to be echoed on the console (perhaps you expect only errors to appear, for instance).
!  with TSQLLog.Family do begin
!    Level := LOG_VERBOSE;
!!    EchoToConsole := LOG_VERBOSE; // log all events to the console
!  end;
Depending on the events, colors will be used to write the corresponding information. Errors will be displayed as light red, for instance.
Note that this echoing process slow down the logging process a lot, since it is currently implemented in a blocking mode, and writing to the console under {\i Windows} is much slower than writing to a file. This feature is therefore disabled by default, and not to be enabled on a production server, but only to make interactive debugging easier.
:104  Remote logging
By default, {\f1\fs20 TSynLog} writes its activity to a local file, and/or to the console. The log file can be transmitted later on (once compressed) to support, for further review and debugging.\line But sometimes, it may be handy to see the logging in real-time, on a remote computer.
You can enable such remote monitoring for a given {\f1\fs20 TSynLog} class, by adding the {\f1\fs20 mORMotHTTPClient.pas} unit in your use clause, then calling the following constructor:
! TSQLHttpClient.CreateForRemoteLogging('192.168.1.15',SQLite3Log,'8091','LogService');
This command will let any {\f1\fs20 SQLite3Log} event be sent to a remote server running at {\f1\fs20 http://192.168.1.15:8091/LogService/RemoteLog} - in fact this should be a {\i mORMot} server, but may be any REST server, able to answer to a {\f1\fs20 PUT} command sent to this URI.
A {\f1\fs20 TSQLHttpClient} instance will be created, and will be managed by the {\f1\fs20 SQLite3Log} instance. It will be released when the application will be closed, or when the {\f1\fs20 SQLite3Log.Family.EchoRemoteStop} method will be called.
In practice, our {\i Log View} tool - see @103@ - is able to run as a compatible remote server. Execute the tool, set the expected {\i Server Root} name ('{\f1\fs20 LogService}' by default), and the expected {\i Server Port} (8091 by default), then click on the "{\f1\fs20 Server Launch}" button.\line The {\i Log View} tool will now display in real time all incoming events, search into their content, and allow to save all received events into a regular {\f1\fs20 .log} or {\f1\fs20 .synlz} file, for further archiving and study.\line Note that since the {\i Log View} tool will run a {\f1\fs20 http.sys} based server - see @88@ - you may have to run once the tool with administrator rights, to register the {\i Server Root} / {\i Server Port} combination for binding.
Implementation of this remote logging has been tuned on both client and server side.\line On client side, log events are gathered and sent in a dedicated background thread: if a lot of events are generated, they will be transferred in chunks of several rows, to minimize resource and bandwidth. On server side, incoming events are stored in memory, and indexed on the fly, with a periodic refresh rate of 500 ms: even a very active client logger would just let the {\i Log View} tool be responsive and efficient.\line Thanks to the nature of the {\f1\fs20 http.sys} based server, several {\i Server Root} URI can be accessed in parallel with several {\i Log View} tool instance, on the same HTTP port: it will ease the IT policy of your network, since a single forwarded port would be able to handle several incoming connections.
See the "{\f1\fs20 RemoteLoggingTest.dpr}" sample from "{\f1\fs20 11 - Exception logging}", in conjunction with the {\f1\fs20 LogView.dpr} tool available in the same folder, for a running example of remote logging.
Note that our cross-platform clients - see @86@ - are able to log to a remote server, with the same exact format as used by our {\f1\fs20 TSynLog} class.
:  Log to third-party libraries
Our {\f1\fs20 TSynLog} class was designed to write its information to a file, and optionally to the console or a remote log server (as we just saw). In fact, {\f1\fs20 TSynLog} is extensively used by the {\i mORMot} framework to provide various levels of details on what happens behind the scene: it is great for debugging purposes.
It may be convenient to let {\f1\fs20 TSynLog} work with any third party logging applications such as {\i CodeSite} or {\i SmartInspect}, or any proprietary solution. As a result, {\i mORMot} logs can be mixed with existing application logs.
You can define the {\f1\fs20 TSynLogFamily.EchoCustom} property to specify a simple event to be triggered for each log operation: the application can then decide to log to a third party logger application.
Note that there is also the {\f1\fs20 TSynLogFamily.NoFile} property, which allows to disable completely the built-in file logging mechanism.
For instance, you may write:
!procedure TMyClass.Echo(Sender: TTextWriter; Level: TSynLogInfo; const Text: RawUTF8);
!begin
!  if Level in LOG_STACKTRACE then // filter only errors
!    writeln(Text); // could be any third-party logger
!end;
!
!...
!  with TSQLLog.Family do begin
!    Level := LOG_VERBOSE;
!    // EchoToConsole := LOG_VERBOSE; // log all events to the console
!!    EchoCustom := aMyClass.Echo; // register third-party logger
!!    NoFile := true; // ensure TSynLog won't use the default log file
!  end;
A process similar to {\f1\fs20 TSynLogFile.ProcessOneLine()} could then parse the incoming {\f1\fs20 Text} value, if needed.
:  Automated log archival
Log archives can be created with the following settings:
! with TSynLogDB.Family do
! begin
!  (...)
!  OnArchive := EventArchiveZip;
!  ArchivePath := '\\Remote\WKS2302\Archive\Logs'; // or any path
! end;
The {\f1\fs20 ArchivePath} property can be set to several functions, taking a timeout delay from the {\f1\fs20 ArchiveAfterDays} property value:
- {\f1\fs20 nil} is the default value, and won't do anything: the {\f1\fs20 .log} will remain on disk until they will be deleted by hand;
- {\f1\fs20 EventArchiveDelete} in order to delete deprecated {\f1\fs20 .log} files;
- {\f1\fs20 EventArchiveSynLZ} to compress the {\f1\fs20 .log} file into a proprietary {\i @*SynLZ@} format: resulting file name will be located in {\f1\fs20 ArchivePath\\log\\YYYYMM\\*.log.synlz}, and the command-line {\f1\fs20 UnSynLz.exe} tool (calling {\f1\fs20 FileUnSynLZ} function of {\f1\fs20 SynCommons.pas} unit) can be used to uncompress it in to plain {\f1\fs20 .log} file;
- {\f1\fs20 SynZip.EventArchiveZip} will archive the {\f1\fs20 .log} files in {\f1\fs20 ArchivePath\\log\\YYYYMM.zip} files, grouping every .
{\i SynLZ} files are less compressed, but created much faster than {\f1\fs20 .zip} files. However, {\f1\fs20 .zip} files are more standard, and on a regular application, compression speed won't be an issue for the application.
:  Log files rotation
You can set {\f1\fs20 TSynLogFamily.RotateFileCount} and {\f1\fs20 RotateFileSizeKB} properties, to enable log file rotation:
- If both values are > 0, the log file will have a fixed name, without any time-stamp within;
- {\f1\fs20 RotateFileSizeKB} will define the maximum size of the main uncompressed log file
- {\f1\fs20 RotateFileCount} will define how many files are kept on disk - note that rotated files are compressed using {\i SynLZ}, so compression will be very fast.
Log file rotation is as easy as:
!  with TSQLLog.Family do begin
!    Level := LOG_VERBOSE;
!    RotateFileCount := 5;        // will maintain a set of up to 5 files
!    RotateFileSizeKB := 20*1024; // rotate by 20 MB logs
!  end;
Such a logging definition will create those files on disk, e.g. for the {\f1\fs20 TestSQL3.dpr} regression tests:
- {\f1\fs20 TestSQL3.log} which will be the latest (current) log file, uncompressed;
- {\f1\fs20 TestSQL3.1.synlz} to {\f1\fs20 TestSQL3.4.synlz} will be the 4 latest log files, after compression. Our {\i Log Viewer} tool - see @103@ - is able to uncompress those {\f1\fs20 .synlz} files directly.
Note that as soon as you active file rotation, {\f1\fs20 PerThreadLog = ptOneFilePerThread} and {\f1\fs20 HighResolutionTimeStamp} properties will be ignored, since both features expect a single file to exist per {\f1\fs20 TSynLog} class.
As an alternative, or in addition to this {\i by-size} rotation pattern, you could specify a fixed time of the day to perform the rotation.\line For instance, the following will perform automatic rotation of the log files, whatever their size, at {\f1\fs20 23:00} each evening:
!  with TSQLLog.Family do begin
!    Level := LOG_VERBOSE;
!    RotateFileCount := 5;        // will maintain a set of up to 5 files
!    RotateFileDailyAtHour := 23; // rotate at 11:00 PM
!  end;
If the default behavior - which is to compress all rotated files into {\f1\fs20 .synlz} format, and delete the older files - does not fit your needs, you can set a custom event to the {\f1\fs20 TSynLogFamily.OnRotate} property, which would take care of the file rotation process.
:  Integration within tests
Logging is integrated within the unit @*test@ing classes, so that any failure will create an entry in the log with the source line, and stack trace:
$C:\Dev\lib\SQLite3\exe\TestSQL3.exe 0.0.0.0 (2011-04-13)
$Host=Laptop User=MyName CPU=2*0-15-1027 OS=2.3=5.1.2600 Wow64=0 Freq=3579545
$TSynLogTest 1.13 2011-04-13 05:40:25
$
$20110413 05402559 fail  TTestLowLevelCommon(00B31D70) Low level common: TDynArray "dynamic array failure" stack trace 0002FE0B SynCommons.TDynArray.Init (15148) 00036736 SynCommons.Test64K (18206) 0003682F SynCommons.TTestLowLevelCommon._TDynArray (18214) 000E9C94 TestSQL3 (163)
The difference between a test suit without logging ({\f1\fs20 TSynTests}) and a test suit with logging ({\f1\fs20 TSynTestsLogged}) is only this overridden method:
!procedure TSynTestsLogged.Failed(const msg: string; aTest: TSynTestCase);
!begin
!  inherited;
!  with TestCase[fCurrentMethod] do
!    fLogFile.Log(sllFail,'%: % "%"',
!      [Ident,TestName[fCurrentMethodIndex],msg],aTest);
!end;
In order to enable tests logging, you have just to enable it, e.g. with:
! TSynLogTestLog.Family.Level := LOG_VERBOSE;
You can optionally redirect the following global variable at program initialization, to share testing events with the main {\i mORMot} logs:
!  with TSQLLog.Family do begin
!    Level := LOG_VERBOSE;
!!    TSynLogTestLog := TSQLLog; // share the same log file with whole mORMot
!  end;
:103  Log Viewer
Since the log files tend to be huge (for instance, if you set the logging for our unitary tests, the 17,000,000 test cases do create a huge log file of about 550 MB), a log viewer was definitively in need.
The log-viewer application is available as source code in the "{\i Samples}" folder, in the "{\i 11 - Exception logging}" sub-folder.
:   Open log files
You can run it with a specified log file on the command line, or use the "{\i Browse}" button to browse for a file. That is, you can associate this tool with your {\f1\fs20 .log} files, for instance, and you'll open it just by double-clicking on such files.
Note that if the file is not in our {\f1\fs20 TSynLog} format, it will still be opened as plain text. You'll be able to browse its content and search within, but all the nice features of our logging won't be available, of course.
It is worth saying that the viewer was designed to be {\i fast}.\line In fact, it takes no time to open any log file. For instance, a 390 MB log file is opened in less than one second on my laptop. Under Windows Seven, it takes more time to display the "Open file" dialog window than reading and indexing the 390 MB content.\line It uses internally memory mapped files and optimized data structures to access to the data as fast as possible - see {\f1\fs20 TSynLogFile} class.
:   Log browser
The screen is divided into three main spaces:
- On the left side, the panel of commands;
- On the right side, the log events list;
- On the middle, an optional list of method calls, and another list of threads (not shown by default).
The command panel allows to {\i Browse} your disk for a {\f1\fs20 .log} file. This button is a toggle of an optional {\i Drive / Directory / File} panel on the leftmost side of the tool. When a {\f1\fs20 .log / .synlz / .txt} file is selected, its content is immediately displayed. You can specify a directory name as a parameter of the tool (e.g. in a {\f1\fs20 .lnk} desktop link), which will let the viewer be opened in "Browse" mode, starting with the specified folder.
A button gives access to the global {\i Stats} about its content (customer-side hardware and software running configuration, general numbers about the log), and even ask for a source code line number and unit name from a hexadecimal address available in the log, by browsing for the corresponding {\f1\fs20 .map} file (could be handy if you did not deliver the {\f1\fs20 .map} content within your main executable - which you should have to, IMHO).
Just below the "{\i Browse}" button, there is an edit field available, with a ? button. Enter any text within this edit field, and it will be searched within the log events list. Search is case-insensitive, and was designed to be fast. Clicking on the ? button (or pressing the {\f1\fs20 F3} key) allows to repeat the last search.
In the very same left panel, you can see all existing events, with its own color and an associated check-box. Note that only events really encountered in the {\f1\fs20 .log} file appear in this list, so its content will change between log files. By selecting / un-selecting a check-box, the corresponding events will be instantaneously displayed / or not on the right side list of events. You can right click on the events check-box list to select a predefined set of events.
The right colored event list follows the events appended to the log, by time order. When you click on an event, its full line content is displayed at the bottom on the screen, in a memo.
Having all @*SQL@ / @*NoSQL@ and @*Client-Server@ events traced in the log is definitively a huge benefit for customer support and bug tracking.
:   Customer-side profiler
One distinctive feature of the {\f1\fs20 TSynLog} logging class is that it is able to map methods or functions entering/leaving (using the {\f1\fs20 Enter} method), and trace this into the logs. The corresponding timing is also written within the "{\i Leave}" event, and allows application profiling from the customer side. Most of the time, profiling an application is done during the testing, with a test environment and database. But this is not, and will never reproduce the exact nature of the customer use: for instance, hardware is not the same (network, memory, CPU), nor the software (Operating System version, [anti-]virus installed)... By enabling customer-side method profiling, the log will contain all relevant information. Those events are named "{\i Enter}" / "{\i Leave}" in the command panel check-box list, and written as + and - in the right-sided event list.
The "{\i Methods profiler}" options allow to display the middle optional method calls list. Several sort order are available: by name (alphabetical sort), by occurrence (in running order, i.e. in the same order than in the event log), by time (the full time corresponding to this method, i.e. the time written within the "{\i Leave}" event), and by proper time (i.e. excluding all time spent in the nested methods).
The "{\i Merge method calls}" check-box allows to regroup all identical method calls, according to their name. In fact, most methods are not called once, but multiple time. And this is the accumulated time spent in the method which is the main argument for code profiling.
I'm quite sure that the first time you'll use this profiling feature on a huge existing application, you'll find out some bottlenecks you would have never thought about before.
:   Per-thread inspection
If the {\f1\fs20 TSynLog} family has specified {\f1\fs20 PerThreadLog := ptIdentifiedInOnFile} property, a new column will be added for each log row, with the corresponding {\f1\fs20 ThreadID} of the logged action.
The log-viewer application will identify this column, and show a "{\i Thread}" group below the left-side commands. It will allow to go to the next thread, or toggle the optional {\i Thread view} list. By checking / un-checking any thread of this list, you are able to inspect the execution log for a given process, very easily. A right-click on this thread list will display a pop-up menu, allowing to select all threads or no thread in one command.
:   Server for remote logging
As was stated above, @104@ can use our {\i Log View} tool as server and real-time viewer for any remote client, either using {\f1\fs20 TSynLog}, or any cross-platform client - see @86@.
Using a remote logging is specially useful from mobile applications (written with {\i Delphi} / @*FireMonkey@ or with @*Smart Mobile Studio@ / @*AJAX@). Our viewer tool allows efficient live debugging of such platforms.
:3Object-Relational Mapping
%cartoon02.png
The @**ORM@ part of the framework - see @13@ - is mainly implemented in the @!Lib\mORMot.pas@ unit. Then it will use other units (like @!Lib\mORMotSQLite3.pas@, @!Lib\mORMotDB.pas@, @!Lib\SynSQLite3.pas@ or @!Lib\SynDB.pas@) to access to the various database back-ends.
Generic access to the data is implemented by defining high-level objects as {\i Delphi} classes, descendant from a main {\f1\fs20 @**TSQLRecord@} class.
In our @*Client-Server@ ORM, those {\f1\fs20 TSQLRecord} classes can be used for at least three main purposes:
- To store and retrieve data from any database engine - for most common usage, you can forget about writing @*SQL@ queries: @*CRUD@ data access statements ({\f1\fs20 SELECT / INSERT / UPDATE /DELETE}) are all created on the fly by the {\i Object-relational mapping} (ORM) core of {\i mORMot} - see @27@ - a @*NoSQL@ engine like {\i @*MongoDB@} can even be accessed the same way - see @84@;
- To have business logic objects accessible for both the Client and Server side, in a @*REST@ful approach - see @114@;
................................................................................
!begin
!  I := Props.Execute('select * from Sales.Customer where AccountNumber like ?',['AW000001%']);
!  while I.Step do
!    assert(Copy(I['AccountNumber'],1,8)='AW000001');
!end;
In this procedure, no {\f1\fs20 TSQLDBStatement} is defined, and there is no need to add a {\f1\fs20 try ... finally Query.Free; end;} block.
In fact, the {\f1\fs20 MyConnProps.Execute} method returns a {\f1\fs20 TSQLDBStatement} instance as a {\f1\fs20 ISQLDBRows}, which methods can be used to loop for each result row, and retrieve individual column values. In the code above, {\f1\fs20 I['FirstName']} will in fact call the {\f1\fs20 I.Column[]} default property, which will return the column value as a {\f1\fs20 variant}. You have other dedicated methods, like {\f1\fs20 ColumnUTF8} or {\f1\fs20 ColumnInt}, able to retrieve directly the expected data.
Note that all bound parameters will appear within the SQL statement, when @*log@ged using our {\f1\fs20 TSynLog} classes - see @16@.
:136  Late-binding
We implemented @*late-binding@ access of column values, via a custom variant time. It uses the internal mechanism used for {\i Ole Automation}, here to access column content as if column names where native object properties.
The resulting {\i Delphi} code to write is just clear and obvious:
!procedure UseProps(Props: TSQLDBConnectionProperties);
!var Row: Variant;
!begin
!  with Props.Execute('select * from Sales.Customer where AccountNumber like ?',
................................................................................
- {\i Not to cache unless you need to} (see Knuth's wisdom);
- {\i Ensure that caching is worth it} (if a value is likely to be overridden often, it could be even slower to cache it);
- Test once, test twice, always test and do not forget to test even more.
In practice, caching issues could be difficult to track. So in case of doubt (why was this data not accurate? it sounds like an old revision?), you may immediately disable caching, then ensure that you were not too optimistic about your cache policy.
:  What to cache
Typical content of these two tuned caches can be any global configuration settings, or any other kind of unchanging data which is not likely to vary often, and is accessed simultaneously by multiple clients, such as catalog information for an on-line retailer.
Another good use of caching is to store data that changes but is accessed by only one client at a time. By setting a cache at the client level for such content, the server won't be called often to retrieve the client-specific data. In such case, the problem of handling concurrent access to the cached data doesn't arise.
Profiling can be necessary to identify which data is to be registered within those caches, either at the client and/or the server side. The logging feature - see @16@ - integrated to {\i mORMot} can be very handy to tune the caching settings, due to its unique customer-side profiling ability.
But most of the time, an human guess at the business logic level is enough to set which data is to be cached on each side, and ensure content coherency.
:  How to cache
A tuned caching mechanism can be defined, for both {\f1\fs20 TSQLRestClient} and {\f1\fs20 TSQLRestServer} classes, at ID level.
By default, REST level cache is disabled, until you call {\f1\fs20 TSQLRest.Cache}'s {\f1\fs20 SetCache()} and {\f1\fs20 SetTimeOut()} methods. Those methods will define the caching policy, able to specify which table(s) or record(s) are to be cached, either at the client or the server level.
Once enabled for a table and a set of IDs on a given table, any further call to {\f1\fs20 TSQLRest.Retrieve(aClass,aID)} or {\f1\fs20 TSQLRecord.Create(aRest,aID)} will first attempt to retrieve the {\f1\fs20 TSQLRecord} of the given {\f1\fs20 aID} from the internal {\f1\fs20 TSQLRestCache} instance's in-memory cache, if available.\line Note that more complex requests, like queries on other fields than the ID @*primary key@, or JOINed queries, won't be cached at REST level. But such requests may benefit of the @37@, at {\i SQLite3} level, on the server side.
For instance, here is how the Client-side caching is tested about one individual record:
!    (...)
................................................................................
If you are using a {\i mORMot} server for the first time, you may be amazed by how most common process would sound just immediate. You can capitalize on the framework optimizations, which are able to unleash the computing power of your hardware, then refine your code only when performance matters.
In order to speed up our data processing, we first have to consider the classic architecture of a {\i mORMot} application - see @%%mORMotDesign3@.\line A {\i mORMot} client would have two means of accessing its data:
- Either from CRUD / ORM methods;
- Or via services, most probably {\i interface-based services} - see @63@.
Our optimization goals would therefore be leaded into those two directions.
:  Profiling your application
If you worry about performance, first reflex may be to enable the framework logging, even on customer sites.
The profiling abilities of the {\f1\fs20 TSynLog} class, used everywhere in our framework, will allow you to identify the potential bottlenecks of your client applications. See @73@ about this feature.\line From our experiment, we can assure you that the first time you may run logging on a real {\i mORMot} application, you may find issues you would never have think off by yourself.
Fight against any duplicated queries, unnecessary returned information (do not forget to specify the fields to be retrieved to your {\f1\fs20 CreateAndFillPrepare()} request), unattended requests triggered by the User Interface (typically a {\f1\fs20 TEdit}'s {\f1\fs20 OnChange} event may benefit of using a {\f1\fs20 TTimer} before asking for auto-completion)...
Once you have done this cleaning, you may be proud of you, and it may be enough for your customers to enjoy your application. You deserve a coffee or a drink.
:  Client-side caching
You may have written most of your business logic on the client side, and use CRUD / ORM methods to retrieve the information from your {\i mORMot} server.\line This is perfectly valid, but may be slow, especially if a lot of individual requests are performed over the network, which may be with high latency (e.g. over the Internet).
A new step of optimization, once you did identify your application bottlenecks via profiling, may be to tune your ORM client cache. In fact, there are several layers of caching available in a {\i mORMot} application. See @39@ for details about these features.\line Marking some tables as potentially cached on the client side may induce a noticeable performance boost, with no need of changing your client code.
:  Write business logic as Services
A further step of optimization may be to let the business logic be processed on the server side, within a @*service@.\line In fact, you would then switch your mind from a classic @7@ to a @17@.
................................................................................
- Remote @*CRUD@ operations, via @*JSON@ and @*REST@, with a {\f1\fs20 TSQLRestClientURI} class, with the same methods as with the {\f1\fs20 mORMot.pas} framework unit;
- Optimized {\f1\fs20 TSQLTableJSON} class to handle a JSON result table, as returned by {\i mORMot}'s REST server ORM - see @87@;
- @*Batch@ process - see @28@ - for transactional and high-speed writes;
- @49@ with parameters marshaling;
- @63@ with parameters marshaling and instance-life time;
- Mapping of most supported field types, including e.g. @*ISO 8601@ date/time encoding, @*BLOB@s and {\f1\fs20 TModTime}/{\f1\fs20 TCreateTime} - see @26@;
- Complex {\f1\fs20 record} types are also exported and consumed via JSON, on all platforms (for both ORM and SOA methods);
- Integrated debugging methods, used by both ORM and SOA process, able to log into a local file or to a remote server, e.g. our @103@;
- Some cross-platform low-level functions and types definitions, to help share as much code as possible between your projects.
In the future, C# or Java clients may be written.\line The {\f1\fs20 CrossPlatform} sub-folder code could be used as reference, to write minimal and efficient clients on any platform. Our REST model is pretty straightforward and standard, and use of JSON tends to leverage a lot of potential marshaling issues which may occur with XML or binary formats.
In practice, a code generator embedded in the {\i mORMot} server can be used to create the client wrappers, using the @81@ included on the server side. With a click, you can generate and download a client source file for any supported platform. A set of {\f1\fs20 .mustache} templates is available, and can be customized or extended to support any new platform: any help is welcome, especially for targeting Java or C# clients.
\page
: Available client platforms
:69  Delphi FMX / FreePascal FCL cross-platform support
Latest versions of {\i Delphi} include the {\i @**FireMonkey@} (FMX) framework, able to deliver multi-device, true native applications for {\i Windows}, {\i Mac @*OSX@}, {\i @*Android@} and {\i iOS} ({\i @*iPhone@}/{\i @*iPad@}).\line Our {\f1\fs20 SynCrossPlatform*} units are able to easily create clients for those platforms.
................................................................................
$xcopy SynCrossPlatformSpecific.pas "c:\ProgramData\Optimale Systemer AS\Smart Mobile Studio\Libraries" /Y
$xcopy SynCrossPlatformCrypto.pas "c:\ProgramData\Optimale Systemer AS\Smart Mobile Studio\Libraries" /Y
$xcopy SynCrossPlatformREST.pas "c:\ProgramData\Optimale Systemer AS\Smart Mobile Studio\Libraries" /Y
You can find a corresponding BATCH file in the {\f1\fs20 CrossPlatform} folder, and in {\f1\fs20 SQLite3\\Samples\\29 - SmartMobileStudio Client\\CopySynCrossPlatformUnits.bat}.
In fact, the {\f1\fs20 SynCrossPlatformJSON.pas} unit is not used under {\i Smart Mobile Studio}: we use the built-in JSON serialization features of {\i JavaScript}, using {\f1\fs20 variant} dynamic type, and the standard {\f1\fs20 JSON.Stringify()} and {\f1\fs20 JSON.Parse()} functions.
:  Remote logging
Since there is no true file system API available under a HTML5 sand-boxed application, logging to a local file is not an option. Even when packaged with {\i PhoneGap}, local log files are not convenient to work with.
Generated logs will have the same methods and format as with {\i Delphi} or {\i FreePascal} - see @105@. {\f1\fs20 TSQLRest.Log(E: Exception)} method will also log the stack trace of the exception!  And our @103@ tool is able to run as efficient remote server.
A dedicated {\i asynchronous} implementation has been refined for {\i Smart Mobile Studio} clients, so that several events would be gathered and sent at once to the remote server, to maximize bandwidth use and let the application be still responsive.\line It allows even complex mobile applications to be debugged with ease, on any device, even over WiFi or 3G/4G networks. Your support could ask your customer to enable logging for a particular case, then see in real time what is wrong with your application.
\page
: Generating client wrappers
Even if it is feasible to write the client code by hand, your {\i mORMot} server is able to create the source code needed for client access, via a dedicated method-based service, and a set of {\i @*Mustache@}-based templates - see @81@.
The following templates are available in the {\f1\fs20 CrossPlatform\\templates} folder:
|%45%45
|\b Unit Name|Compiler Target\b0
................................................................................
\Domain¤REST Server\ORM
\Domain¤Services\ORM
\
In order to provide the better scaling of the server side, @*cache@ can be easily implemented at every level, and hosting can be tuned in order to provide the best response time possible: one central server, several dedicated servers for application, domain and persistence layers...
Due to the @*SOLID@ design of {\i mORMot} - see @47@ - you can use as many Client-Server services layers as needed in the same architecture (i.e. a Server can be a Client of other processes), in order to fit your project needs, and let it evolve from the simplest architecture to a full scalable {\i Domain-Driven} design.
:12Testing and logging
%cartoon05.png


: Automated testing
You know that @**test@ing is (almost) everything if you want to avoid regression problems in your application.
How can you be confident that any change made to your software code won't create any error in other part of the software?
Automated unit testing is a good candidate for avoiding any serious regression.
And even better, testing-driven coding can be encouraged:
- Write a void implementation of a feature, that is code the interface with no implementation;
- Write a test code;
................................................................................
- Launch the test - it must fail;
- Implement the feature;
- Launch the test - it must pass;
- Add some features, and repeat all previous tests every time you add a new feature.
It could sounds like a waste of time, but such coding improve your code quality a lot, and, at least, it help you write and optimize every implementation feature.
The framework has been implemented using this approach, and provide all the tools to write tests. In addition to what other {\i Delphi} frameworks offer (e.g. {\i DUnit / DUnitX}), it is very much integrated with other elements of the framework (like logging), and provide a complete {\i stubbing / mocking} mechanism to cover @62@.
:  Involved classes in Unitary testing
The @!TSynTest,TSynTestCase,TSynTests!Lib\SynCommons.pas@ unit defines two classes (both inheriting from {\f1\fs20 TSynTest}), implementing a complete Unitary testing mechanism similar to {\i DUnit}, with less code overhead, and direct interface with the framework units and requirements (@*UTF-8@ ready, code compilation from {\i Delphi} 6 up to XE7, no external dependency).
The following diagram defines this class hierarchy:
\graph HierTSynTest TSynTest classes hierarchy
\TSynTests\TSynTest
\TSynTestCase\TSynTest
\
The main usable class types are:
- {\f1\fs20 TSynTestCase}, which is a class implementing a test case: individual tests are written in the published methods of this class;
................................................................................
$Tests performed at 25/03/2014 10:59:33
$
$Total assertions failed for all test suits:  0 / 4,000
$! All tests passed successfully.
You can see that all text on screen was created by "UnCamelCasing" the method names (thanks to our good old @*Camel@), and that the test suit just follows the order defined when registering the classes. Each method has its own timing, which is pretty convenient to track performance regressions.
This test program has been uploaded in the {\f1\fs20 SQLite3\\Sample\\07 - SynTest} folder of the Source Code Repository.
:  Implemented tests
The @SAD-DI-2.2.2@ defines all classes released with the framework source code, which covers all core aspects of the framework. Global testing coverage is good, excellent for core components (more than 35,000,000 individual checks are performed for revision 1.18), but there is still some User-Interface related tests to be written.
Before any release all unitary regression tests are performed with the following compilers:
- {\i Delphi} 5 (for a limited scope, including {\f1\fs20 SynCommons.pas}, {\f1\fs20 SynPdf.pas} and {\f1\fs20 SynDB.pas});
- {\i Delphi} 6;
- {\i Delphi} 7, with and without our Enhanced Run Time Library;
- {\i Delphi} 2007;
- {\i Delphi} 2010 (we assume that if it works with {\i Delphi} 2010, it will work with {\i Delphi} 2009, with the exception of {\f1\fs20 generic} compilation);
- {\i Delphi} XE4;
- {\i Delphi} XE6.


Then all sample source code (including the {\i Main Demo} and {\f1\fs20 @*SynDBExplorer@} sophisticated tools) are compiled, and user-level testing is performed against those applications.
You can find in the {\f1\fs20 compil.bat} and {\f1\fs20 compilpil.bat} files of our source code repository how incremental builds and tests are performed.
\page




















:73 Logging























































































































































































































































































The framework makes an extensive use of the logging features implemented in the {\f1\fs20 SynCommons.pas} unit - see @16@.
In its current implementation, the framework is able to log on request:
- Any exceptions triggered during process, via {\f1\fs20 sllException} and {\f1\fs20 sllExceptionOS} levels;
- Client and server @*REST@ful {\f1\fs20 URL} methods via {\f1\fs20 sllClient} and {\f1\fs20 sllServer} levels;
- @*SQL@ executed statements in the {\i @*SQLite3@} engine via the {\f1\fs20 sllSQL} level;
- @*JSON@ results when retrieved from the {\i SQLite3} engine via the {\f1\fs20 sllResult} level;
- Main errors triggered during process via {\f1\fs20 sllError} level;
- @*Security@ User authentication and @*session@ management via {\f1\fs20 sllUserAuth};
................................................................................
The framework source code tree will compile and is tested for the following platforms:
- {\i Delphi} 6 up to {\i Delphi} XE7 compiler and IDE, with @*FPC@ 2.7.1 support;
- Server side on Windows 32 bit and @**64 bit@ platforms ({\i Delphi} XE2 and up is expected when targeting {\i Win64});
- Preliminary @*Linux@ platform for @*ORM@ servers using the FPC compiler - not yet stable enough to be used on production, and @*SOA@ or Web @*MVC@ not yet working due to a FPC bug - see @112@;
- VCL client on Win32/Win64 - GUI may be compiled optionally with third-party non Open-Source @*TMS@ Components, instead of default VCL components - see @http://www.tmssoftware.com/site/tmspack.asp
- @69@ clients on any supported platforms;
- @90@ startup with 2.1, for creating AJAX / HTML5 / Mobile clients.
Some part of the library (e.g. {\f1\fs20 SynCommons.pas}, {\f1\fs20 SynPDF.pas} or the @27@ units) are also compatible with {\i Delphi} 5.
If you want to compile {\i mORMot} unit into @*packages@, to avoid an obfuscated {\i [DCC Error] @*E2201@ Need imported data reference ($G) to access 'VarCopyProc'} error at compilation, you should defined the {\f1\fs20 USEPACKAGES} conditional in your project's options. Open {\f1\fs20 SynCommons.inc} for a description of this conditional, and all over definitions global to all {\i mORMot} units - see @45@.
Note that the framework is expected to create only Windows server applications yet.\line But @86@ are available, using either {\i @*FireMonkey@} (FMX) library for User Interface generation, {\i @*FreePascal@ Compiler} (FPC) / {\i @*Lazarus@} support, or other tools more neutral, using @*JavaScript@ and @*AJAX@ via {\i @*Smart Mobile Studio@} - or both. The framework source code implementation and design tried to be as cross-platform as possible, since the beginning.
For HTML5 and Mobile clients, our main platform is {\i Smart Mobile Studio}, which is a great combination of ease of use, a powerful {\i SmartPascal} dialect, small applications (much smaller than FMX), with potential packaging as native iOS or {\i Android} applications (via {\i @*PhoneGap@}).
The latest versions of the {\i FreePascal Compiler} together with its great {\i Lazarus} IDE, are now very stable and easy to work with. I've tried for instance the {\i CodeTyphon} release (which is not the stable branch, but the latest version of both FPC and {\i Lazarus}) - see @http://www.pilotlogic.com - and found it to be impressive. This is amazing to build the whole set of compilers and IDE, with a lot of components, for several platforms (this is a cross-platform project), just from the sources. I like {\i Lazarus} stability and speed much more than {\i Delphi} (did you ever tried to browse and debug {\i included} {\f1\fs20 $I ...} files in the {\i Delphi} IDE? with Lazarus, it is painless), even if the compiler is slower than {\i Delphi}'s, and if the debugger is less integrated and even more unstable than {\i Delphi}'s under Windows (yes, it is possible!). At least, it works, and works pretty well. Official {\i @*Linux@} / {\i FPC} support is available for {\i mORMot} servers - thanks to Alfred! - but this platform is brand new to the framework, so less stable and not yet feature complete.
:  32 bit sqlite3*.obj and 64 bit SQLite3 dll
In order to maintain the source code repository in a decent size, we excluded the {\f1\fs20 sqlite3*.obj} storage in it, but provide the full source code of the {\i @*SQlite3@} engine in the corresponding {\f1\fs20 sqlite3.c} file, ready to be compiled with all conditional defined as expected by {\f1\fs20 SynSQlite3Static.pas}.
Therefore, {\f1\fs20 sqlite3.obj} and {\f1\fs20 sqlite3fts.obj} files are available as a separated download, from @http://synopse.info/files/sqlite3obj.7z
................................................................................
|{\f1\fs20 SynBz.pas bunzipasm.inc}|fast BZ2 compression/decompression
|{\f1\fs20 SynBzPas.pas}|pascal implementation of BZ2 decompression
|{\f1\fs20 SynCommons.pas}|common functions used by most Synopse projects
|{\f1\fs20 SynCrtSock.pas}|classes implementing @*HTTP@/1.1 client and server protocol
|{\f1\fs20 SynCrypto.pas}|fast cryptographic routines (hashing and cypher)
|{\f1\fs20 SynDprUses.inc}|generic header included in the beginning of the uses clause of a .dpr source code
|{\f1\fs20 SynGdiPlus.pas}|GDI+ library API access with anti-aliasing drawing

|{\f1\fs20 SynLZ.pas}|@**SynLZ@ compression decompression unit - used by {\f1\fs20 SynCommons.pas}
|{\f1\fs20 SynLZO.pas}|LZO compression decompression unit
|{\f1\fs20 SynMemoEx.pas}|Synopse extended {\f1\fs20 TMemo} visual component (used e.g. in {\i @*SynProject@}) - for pre-Unicode {\i Delphi} only
|{\f1\fs20 SynMongoDB.pas}|Direct {\i @*MongoDB@} @*NoSQL@ database access
|{\f1\fs20 SynMustache.pas}|{\i @*Mustache@} logic-less template engine
|{\f1\fs20 SynPdf.pas}|@*PDF@ file generation unit
|{\f1\fs20 SynScaleMM.pas}|multi-thread friendly memory manager unit - not finished yet
................................................................................
|{\f1\fs20 SynSelfTests.pas}|automated @*test@s for {\i mORMot} Framework
|{\f1\fs20 SynSMAPI.pas}|{\i SpiderMonkey} JavaScript engine API definition
|{\f1\fs20 SynSM.pas}|{\i SpiderMonkey} JavaScript engine higher level classes
|{\f1\fs20 SynSQLite3.pas}|{\i @*SQLite3@} embedded Database engine
|{\f1\fs20 SynSQLite3Static.pas}|statically linked @*SQLite3@ engine (for Win32)
|{\f1\fs20 SynSSPIAuth.pas}|low level access to Windows Authentication
|{\f1\fs20 SynTaskDialog.*}|implement TaskDialog window\line (native on Vista/Seven, emulated on XP)

|{\f1\fs20 SynWinSock.pas}|low level access to network Sockets for the Windows platform
|{\f1\fs20 SynZip.pas deflate.obj trees.obj}|low-level access to ZLib compression, 1.2.5
|{\f1\fs20 SynZipFiles.pas}|high-level access to .zip archive file compression
|{\f1\fs20 Synopse.inc}|generic header to be included in all units to set some global conditional definitions
|{\f1\fs20 vista.*}|A resource file enabling theming under XP
|{\f1\fs20 vistaAdm.*}|A resource file enabling theming under XP and Administrator rights under Vista
|%
................................................................................
|\b File|Description\b0
|{\f1\fs20 @*SynDB@.pas}|abstract database direct access classes
|{\f1\fs20 SynOleDB.pas}|fast @*OleDB@ direct access classes
|{\f1\fs20 SynDBODBC.pas}|fast @*ODBC@ direct access classes
|{\f1\fs20 SynDBOracle.pas}|{\i @*Oracle@} DB direct access classes (via OCI)
|{\f1\fs20 SynDBSQLite3.pas}|{\i @*SQLite3@} direct access classes
|{\f1\fs20 SynDBDataset.pas}|{\f1\fs20 @*TDataSet@} / {\f1\fs20 @*TQuery@}-like access classes\line (drivers included in {\f1\fs20 SynDBDataset} sub-folder)

|{\f1\fs20 SynDBVCL.pas}|DB VCL read-only dataset using {\f1\fs20 SynDB.pas} data access
|{\f1\fs20 SynDBZEOS.pas}|{\i @*Zeos@Lib} / ZDBC direct access classes
|%
:   SynDBDataset folder
In a {\f1\fs20 SynDBDataset} folder, some external database providers are available, to be used with the {\f1\fs20 SynDBDataset.pas} classes:
|%30%70
|\b File|Description\b0
................................................................................
: Upgrading from a 1.17 revision
If you are upgrading from an older revision of the framework, your own source code should be updated.
For instance, some units where renamed, and some breaking changes introduced by enhanced features. As a consequence, a direct update is not possible.
To properly upgrade to the latest revision:
1. Erase or rename your whole previous {\f1\fs20 #\\Lib} directory.
2. Download latest 1.18 revision files as stated just above.
3. Change your references to {\i mORMot} units:


- Rename in your uses clauses any {\f1\fs20 SQLite3Commons} reference into {\f1\fs20 mORMot.pas};
- Rename in your uses clauses any {\f1\fs20 SQLite3} reference into {\f1\fs20 mORMotSQLite3.pas};
- Rename in your uses clauses any other {\f1\fs20 SQlite3*} reference into {\f1\fs20 mORMot*};
- Add in one of your uses clause a reference to the {\f1\fs20 SynSQLite3Static.pas} unit (for {\i Win32} or {\i Linux}).
4. Consult the units' headers about 1.18 for breaking changes, mainly:
- Introducing {\f1\fs20 @*TID@ = type Int64} as {\f1\fs20 TSQLRecord.ID} @*primary key@, {\f1\fs20 TIDDynArray} as an array, and @*TRecordReference@ now declared as {\f1\fs20 Int64} instead of plain {\f1\fs20 PtrInt} / {\f1\fs20 integer};
- Renamed {\f1\fs20 Iso8601} low-level structure as {\f1\fs20 @*TTimeLogBits@};







|
|




|







 







|












>
>










>



|







 







>











|






|







 







|
|







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







|







 







|







 







|







 







|







 







|







 







>
>







 







|







 







|

|





|
>
>



>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|







 







|







 







>







 







>







 







>







 







>
>







738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
....
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
....
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
....
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
....
1802
1803
1804
1805
1806
1807
1808











































































































































































































































































































1809
1810
1811
1812
1813
1814
1815
....
4677
4678
4679
4680
4681
4682
4683
4684
4685
4686
4687
4688
4689
4690
4691
....
7398
7399
7400
7401
7402
7403
7404
7405
7406
7407
7408
7409
7410
7411
7412
....
7438
7439
7440
7441
7442
7443
7444
7445
7446
7447
7448
7449
7450
7451
7452
....
9668
9669
9670
9671
9672
9673
9674
9675
9676
9677
9678
9679
9680
9681
9682
....
9814
9815
9816
9817
9818
9819
9820
9821
9822
9823
9824
9825
9826
9827
9828
.....
12193
12194
12195
12196
12197
12198
12199
12200
12201
12202
12203
12204
12205
12206
12207
12208
.....
12209
12210
12211
12212
12213
12214
12215
12216
12217
12218
12219
12220
12221
12222
12223
.....
12315
12316
12317
12318
12319
12320
12321
12322
12323
12324
12325
12326
12327
12328
12329
12330
12331
12332
12333
12334
12335
12336
12337
12338
12339
12340
12341
12342
12343
12344
12345
12346
12347
12348
12349
12350
12351
12352
12353
12354
12355
12356
12357
12358
12359
12360
12361
12362
12363
12364
12365
12366
12367
12368
12369
12370
12371
12372
12373
12374
12375
12376
12377
12378
12379
12380
12381
12382
12383
12384
12385
12386
12387
12388
12389
12390
12391
12392
12393
12394
12395
12396
12397
12398
12399
12400
12401
12402
12403
12404
12405
12406
12407
12408
12409
12410
12411
12412
12413
12414
12415
12416
12417
12418
12419
12420
12421
12422
12423
12424
12425
12426
12427
12428
12429
12430
12431
12432
12433
12434
12435
12436
12437
12438
12439
12440
12441
12442
12443
12444
12445
12446
12447
12448
12449
12450
12451
12452
12453
12454
12455
12456
12457
12458
12459
12460
12461
12462
12463
12464
12465
12466
12467
12468
12469
12470
12471
12472
12473
12474
12475
12476
12477
12478
12479
12480
12481
12482
12483
12484
12485
12486
12487
12488
12489
12490
12491
12492
12493
12494
12495
12496
12497
12498
12499
12500
12501
12502
12503
12504
12505
12506
12507
12508
12509
12510
12511
12512
12513
12514
12515
12516
12517
12518
12519
12520
12521
12522
12523
12524
12525
12526
12527
12528
12529
12530
12531
12532
12533
12534
12535
12536
12537
12538
12539
12540
12541
12542
12543
12544
12545
12546
12547
12548
12549
12550
12551
12552
12553
12554
12555
12556
12557
12558
12559
12560
12561
12562
12563
12564
12565
12566
12567
12568
12569
12570
12571
12572
12573
12574
12575
12576
12577
12578
12579
12580
12581
12582
12583
12584
12585
12586
12587
12588
12589
12590
12591
12592
12593
12594
12595
12596
12597
12598
12599
12600
12601
12602
12603
12604
12605
12606
12607
12608
12609
12610
12611
12612
12613
12614
12615
12616
12617
12618
12619
12620
12621
12622
12623
12624
12625
12626
12627
12628
12629
12630
12631
12632
12633
12634
12635
12636
12637
12638
12639
12640
12641
12642
12643
.....
12694
12695
12696
12697
12698
12699
12700
12701
12702
12703
12704
12705
12706
12707
12708
.....
12749
12750
12751
12752
12753
12754
12755
12756
12757
12758
12759
12760
12761
12762
12763
.....
12764
12765
12766
12767
12768
12769
12770
12771
12772
12773
12774
12775
12776
12777
12778
.....
12781
12782
12783
12784
12785
12786
12787
12788
12789
12790
12791
12792
12793
12794
12795
.....
12861
12862
12863
12864
12865
12866
12867
12868
12869
12870
12871
12872
12873
12874
12875
12876
- If you are still using an old version of {\i Delphi}, and can't easily move up due to some third party components or existing code base, {\i mORMot} will offer all the needed features to start ORM, N-Tier and SOA, starting with a {\i Delphi} 6 edition.
{\i mORMot} implements the needed techniques for introducing what Michael Feathers calls, in his book {\i Working Effectively With Legacy Code}, a @**seam@. A seam is an area where you can start to cleave off some legacy code and begin to introduce changes. Even mocking abilities of {\i mORMot} - see @62@ - will help you in this delicate task - see @http://www.infoq.com/articles/Utilizing-Logging
Do not forget that {\i Synopse}, as a company, is able to offer dedicated audit and support for such a migration. The sooner, the better.
\page
:123 FAQ
Before you start going any further, we propose here below a simple @**FAQ@ containing the most frequent questions we received on our forums.
First of all, take a look at the {\i keyword index} available at the very beginning of this document. The underlined entries targets the main article about a given concept or technical term.
Feel free to give your feedback at @http://synopse.info/forum asking new questions or improving answers!
{\b Your SAD doc is too long to read through in a short period.}\line Too much documentation can kill the documentation! But you do not need to read the whole document: most of it is a detailed description of every unit, object, or class. But the first part is worth reading, otherwise you are very likely to miss some main concepts or patterns. It just takes 15-30 minutes! Consider also the  slides available at @https://drive.google.com/folderview?id=0B0r8u-FwvxWdeVJVZnBhSEpKYkE
{\b Where should I start?}\line Take a look at the {\i Architecture principles} @40@, then download and install the sources and compile and run the {\f1\fs20 TestSQL3.dpr} program as stated @113@. Check about @*ORM@ @3@, @*SOA@ @63@ and @*MVC@ @108@, then test the various samples (from the {\f1\fs20 SQLite3\\Samples} folder), especially 01, 02, 04, 11, 12, 14, 17, 26, 28, 30 and the {\f1\fs20 MainDemo}.
{\b So far, I can see your {\i mORMot} fits most of the requirement, but seems only for Database Client-Server apps.}\line First of all, the framework is a {\i set of bricks}, so you can use it e.g. to build interface based services, even with no database at all. We tried to make its main features modular and uncoupled.
{\b I am not a great fan of ORM, sorry, I still like SQL and have some experience of that. Some times sophisticated SQL query is hard to change to ORM code.}\line ORM can make development much easier; but you can use e.g. interface-based services and "manual" SQL statements - in this case, you have at hand @27@ classes in {\i mORMot}, which allow very high performance and direct export to JSON.
{\b I am tempted by using an ORM, but {\i mORMot} forces you to inherit from a root {\f1\fs20 @*TSQLRecord@} type, whereas I'd like to use any kind of object.}\line Adding attributes to an existing class is tempting, but would pollute your code at the end, mixing persistence and business logic: see {\i @*Persistence Ignorance@} and {\i @*Aggregates@} @124@. Our ORM does not rely on {\i generics}, but on the power of the object pascal {\f1\fs20 class} used with {\i @*convention over configuration@} - so our code is faster, and works with older versions of Delphi, or FreePascal.
{\b I also notice in your SAD doc, data types are different from Delphi. You have {\f1\fs20 RawUTF8}, etc, which make me puzzled, what are they?}\line You can for sure use standard {\i Delphi} {\f1\fs20 string} types, but some more optimized types were defined: since the whole framework is @*UTF-8@ based, we defined a dedicated type, which works with all versions of {\i Delphi}, before and after {\i Delphi} 2009. By the way, just search for {\f1\fs20 RawUTF8} in the {\i keyword index} of this document.
{\b All the objects seem non-VCL components, meaning need code each property and remember them all well.}\line This is indeed... a feature. The framework is not @*RAD@, but fully object-oriented. Thanks to the {\i Delphi} IDE, you can access all properties description via auto-completion and/or code navigation. Then you can still use RAD for UI design, but let business be abstracted in pure code.
{\b I know you have joined the {\i DataSnap} performance discussion and your performance won good reputation there. If I want to use your framework to replace my old project of DataSnap, how easy will it be?}\line If you used {\i DataSnap} to build method-based services, translation into {\i mORMot} would be just a matter of code refactoring. And you will benefit of new features like {\i Interface-based services} - see @63@ - which is much more advanced than the method-based pattern, and will avoid generating the client class via a wizard, and offers additional features - see @77@ or @72@. If you used {\i DataSnap} to access a remote database and linked to VCL components, see the {\f1\fs20 mORMotVCL.pas} unit which can publish a data source for your UI.
{\b What is the SMS? Do you know any advantage compared to JQuery?}\line {\i @*Smart Mobile Studio@} is an IDE and some source runtime able to develop and compile an Object-Pascal project into a {\i @*HTML 5@ / @*CSS 3@ / @*JavaScript@} {\i embedded} application, i.e. able to work stand alone with no remote server. When used with {\i mORMot} on the server side, you can use the very same object pascal language on both server and client sides, with strong typing and true @*OOP@ design. Then you feature secure authentication and JSON communication, with connected or off-line mode. Your {\i SmartPascal} client code can be generated by your {\i mORMot} server, as stated @90@.
{\b I am trying to search a substitute solution to WebSnap. Do you have any sample or doc to describe how to build a robust web Server?}\line You can indeed easily create a modern @*MVC@ / @*MVVM@ scaling @*Web Application@. Your {\i mORMot} server can easily publish its ORM / SOA business logic as {\i Model}, use {\i @*Mustache@} logic-less templates rendering - see @81@ - for {\i Views}, and defining the {\i ViewModel} / {\i Controller} as regular Delphi methods. See @108@ for more details, and discovering a sample "blog" application.
{\b Have you considered using a popular source coding host like @*Github@ or BitBucket?}\line We love to host our own source code repository, and find fossil a perfect match for our needs, with a friendly approach. But we created a parallel repository on {\i GitHub}, so that you may be able to monitor or fork our projects - see @http://github.com/synopse/mORMot \line Note that you can get a daily snapshot of our official source code repository directly from\line @http://synopse.info/files/mORMotNightlyBuild.zip
{\b Why is this framework named {\i mORMot}?}\line - Because its initial identifier was "{\i Synopse SQLite3 database framework}", which may induce a {\i SQLite3}-only library, whereas the framework is now able to connect to any database engine;\line - Because we like mountains, and those large ground rodents;\line - Because marmots do hibernate, just like our precious objects;\line - Because marmots are highly social and use loud whistles to communicate with one another, just like our applications are designed not to be isolated;\line - Because even if they eat greens, they use to fight at Spring for their realm;\line - Because it may be an acronym for "Manage Object Relational Mapping Over Territory", or whatever you may think of...
\page
................................................................................
;In the following sections, the {\i Synopse mORMot Framework} library architecture is detailed as:
;\SourcePage

[SAD-Main]
SourcePath=Lib\SQLite3
IncludePath=Lib;Lib\SQLite3;Lib\SynDBDataset;Lib\CrossPlatform;Zeos\src
;Lib\SQLite3\Samples\MainDemo
SourceFile=TestSQL3.dpr;SynLog.pas;SynTests.pas;mORMot.pas;mORMoti18n.pas;mORMotToolBar.pas;mORMotUI.pas;mORMotUIEdit.pas;mORMotMVC.pas;mORMotUILogin.pas;mORMotReport.pas;mORMotUIOptions.pas;mORMotUIQuery.pas;mORMotService.pas;mORMotSQLite3.pas;mORMotHttpClient.pas;mORMotHttpServer.pas;SynSQLite3.pas;SynSQLite3Static.pas;SynSQLite3RegEx.pas;SynDB.pas;SynOleDB.pas;SynDBOracle.pas;SynDBSQLite3.pas;SynDBODBC.pas;SynDBDataset.pas;SynDBZeos.pas;SynDBFireDAC.pas;SynDBUniDAC.pas;SynDBBDE.pas;SynDBNexusDB.pas;SynDBVCL.pas;mORMotReport.pas;mORMotVCL.pas;mORMotDB.pas;mORMotFastCGIServer.pas;SynSM.pas;SynDBMidasVCL.pas;mORMotMidasVCL.pas;SynMongoDB.pas;SynCrossPlatformJSON.pas;SynCrossPlatformREST.pas;SynCrossPlatformSpecific.pas;SynCrossPlatformTests.pas
;Samples\MainDemo\SynFile.dpr
Version=1.18
TitleOffset=0
DisplayName=mORMot Framework

:Enter new territory
: Meet the mORMot
The {\i Synopse mORMot} framework consists in a huge number of units, so we will start by introducing them.
\graph mORMotSourceCodeMainUnits mORMot Source Code Main Units
node [shape="box"];
rankdir=LR;
\mORMot.pas\SynCommons.pas
\mORMot.pas\SynTests.pas
\mORMot.pas\SynLog.pas
\mORMotDB.pas\mORMot.pas
\mORMotDB.pas\SynCommons.pas
\mORMotDB.pas\SynDB.pas
\UI\mORMot.pas
\UI\SynCommons.pas
\mORMotSQlite3.pas\SynCommons.pas
\mORMotSQlite3.pas\mORMot.pas
\mORMotSQlite3.pas\SynSQLite3.pas¤SynSQLite3Static.pas
\SynSQLite3.pas¤SynSQLite3Static.pas\SynCommons.pas
\SynDB.pas\SynCommons.pas
\SynDB.pas\SynLog.pas
\SynDBSQlite3.pas\SynDB.pas
\SynDBSQlite3.pas\SynCommons.pas
\SynDBSQlite3.pas\SynSQLite3.pas¤SynSQLite3Static.pas
\SynDBOracle.pas¤SynDBODBC.pas¤SynOleDB.pas¤SynDBZEOS.pas¤SynDBDataset.pas¤SynDBFireDAC.pas¤SynDBUniDAC.pas¤SynDBNexusDB.pas¤SynDBBDE.pas¤SynDBRemote.pas\SynDB.pas
\mORMotDB.pas\SynSQLite3.pas¤SynSQLite3Static.pas
\mORMotReport.pas\SynCommons.pas
\mORMotReport.pas\SynPdf.pas
\SynPdf.pas\SynCommons.pas
\SynSMAPI.pas¤SynSM.pas\SynCommons.pas
\HTTP\SynCrtSock.pas
\HTTP\mORMot.pas
................................................................................
\HTTP\SynCommons.pas
\mORMotMongoDB.pas\SynMongoDB.pas
\mORMotMongoDB.pas\mORMot.pas
\mORMotMVC.pas\mORMot.pas
\mORMotMVC.pas\SynCommons.pas
\mORMotMVC.pas\SynMustache.pas
\SynMongoDB.pas\SynCommons.pas
\SynMongoDB.pas\SynLog.pas
\SynMongoDB.pas\SynCrtSock.pas
=UI=mORMotUI.pas¤mORMotToolBar.pas¤mORMoti18n.pas¤mORMotUILogin.pas¤mORMotUIEdit.pas¤mORMotUIQuery.pas¤mORMotUIOptions.pas
=HTTP=mORMotHttpClient.pas¤mORMotHttpServer.pas
\mORMotReport.pas=UI=SynSMAPI.pas¤SynSM.pas
\
;When you will finish reading this document, you won't be afraid any more! :)
\page
: Main units
The main units you have to be familiar with are the following:
|%30%70
|\b Unit name|Description\b0
|{\f1\fs20 SynCommons.pas}\line {\f1\fs20 SynLog.pas}\line {\f1\fs20 SynTests.pas}|Common types, classes and functions
|{\f1\fs20 mORMot.pas}|Main unit of the @*ORM@ / @*SOA@ framework
|{\f1\fs20 SynSQLite3.pas\line SynSQLite3Static.pas}|{\i @*SQLite3@} database engine
|{\f1\fs20 mORMotSQLite3.pas}|Bridge between {\f1\fs20 mORMot.pas} and {\f1\fs20 SynSQLite3.pas}
|{\f1\fs20 @*SynDB@.pas\line SynDB*.pas}|Direct RDBMS access classes
|{\f1\fs20 mORMotDB.pas}|ORM external {\f1\fs20 SynDB.pas} access, via {\i SQlite3} virtual tables
|{\f1\fs20 SynMongoDB.pas\line mORMotMongoDB.pas}|Direct access to a {\i @*MongoDB@} server
|{\f1\fs20 SynSM.pas}\line {\f1\fs20 SynSMAPI.pas}|{\i SpiderMonkey} JavaScript engine
|{\f1\fs20 mORMotHttpClient.pas\line mORMotHttpServer.pas\line SynCrtSock.pas}|@*REST@ful HTTP/1.1 Client and Server
|{\f1\fs20 mORMotMVC.pas\line SynMustache.pas}|@*MVC@ classes for writing @*Web Application@s
|{\f1\fs20 mORMotUI*.pas}|Grid and Forms User Interface generation
|{\f1\fs20 mORMotToolBar.pas}|ORM ToolBar User Interface generation
|{\f1\fs20 mORMotReport.pas}|Integrated Reporting engine
|%
Other units are available in the framework source code repository, but are either expected by those files above (e.g. like {\f1\fs20 SynDB*.pas} database providers), or used only optionally in end-user @*cross-platform@ client applications (e.g. the {\f1\fs20 CrossPlatform} folder).
................................................................................
First of all, a {\f1\fs20 @*Synopse.inc@} include file is provided, and appears in most of the framework units:
!{$I Synopse.inc} // define HASINLINE USETYPEINFO CPU32 CPU64
It will define some conditionals, helping write portable and efficient code.
In the following next paragraphs, we'll comment some main features of the lowest-level part of the framework, mainly located in @!Lib\SynCommons.pas@:
- Unicode and @*UTF-8@;
- {\f1\fs20 @*Currency@} type;
- {\i @*Dynamic array@} wrappers ({\f1\fs20 TDynArray} and {\f1\fs20 TDynArrayHashed});
- {\f1\fs20 @*TDocVariant@} custom {\f1\fs20 variant} type for dynamic schema-less {\i object} or {\i array} storage.
Other shared features available in {\f1\fs20 SynTests.pas} and {\f1\fs20 SynLog.pas} would be detailed later, i.e. @*Test@ing and @*Log@ging - see @12@.
:32 Unicode and UTF-8
Our {\i mORMot} Framework has 100% UNICODE compatibility, that is compilation under {\i Delphi} 2009 and up (including latest XE7 revision). The code has been deeply rewritten and @*test@ed, in order to provide compatibility with the {\f1\fs20 String=UnicodeString} paradigm of these compilers.  But the code will also handle safely Unicode for older versions, i.e. from {\i Delphi} 6 up to {\i Delphi} 2007.
Since our framework is natively @**UTF-8@ (this is the better character encoding for fast @*JSON@ streaming/parsing and it is natively supported by the {\i @*SQLite3@} engine), we had to establish a secure way our framework used strings, in order to handle all versions of {\i Delphi} (even pre-Unicode versions, especially the {\i Delphi} 7 version we like so much), and provide compatibility with the {\i @*FreePascal@ Compiler}.
Some string types have been defined, and used in the code for best cross-compiler efficiency (avoiding most conversion between formats):
- {\f1\fs20 @**RawUTF8@} is used for every internal data usage, since both {\i SQLite3} and JSON do expect UTF-8 encoding;
- {\f1\fs20 WinAnsiString} where {\i WinAnsi}-encoded {\f1\fs20 AnsiString} (code page 1252) are needed;
- Generic {\f1\fs20 string} for {\i @*i18n@} (e.g. in unit {\f1\fs20 mORMoti18n}), i.e. text ready to be used within the VCL, as either {\f1\fs20 AnsiString} (for {\i Delphi} 2 to 2007) or {\f1\fs20 UnicodeString} (for {\i Delphi} 2009 and later);
................................................................................
- Client-Server ORM - see @3@ - will support {\f1\fs20 TDocVariant} in any of the {\f1\fs20 TSQLRecord variant} published properties (and store them as @*JSON@ in a text column);
- Interface-based services - see @63@ - will support {\f1\fs20 TDocVariant} as {\f1\fs20 variant} parameters of any method, which make them as perfect {\i DTO};
- Since JSON support is implemented with any {\f1\fs20 TDocVariant} value from the ground up, it makes a perfect fit for working with AJAX clients, in a script-like approach;
- If you use our {\f1\fs20 SynMongoDB.pas mORMotMongoDB.pas} units to access a {\i @*MongoDB@} server, {\f1\fs20 TDocVariant} will be the native storage to create or access nested @*BSON@ arrays or objects documents - that is, it will allow proper @*ODM@ storage;
- Cross-cutting features (like logging or {\f1\fs20 record} / {\i dynamic array} enhancements) will also benefit from this {\f1\fs20 TDocVariant} custom type.
We are pretty convinced that when you will start playing with {\f1\fs20 TDocVariant}, you won't be able to live without it any more. It introduces the full power of @*late-binding@ and dynamic schema-less patterns to your application code, which can be pretty useful for prototyping or in Agile development. You do not need to use scripting engines like {\i Python} or {\i JavaScript}: {\i Delphi} is perfectly able to handle dynamic coding!
\page











































































































































































































































































































:3Object-Relational Mapping
%cartoon02.png
The @**ORM@ part of the framework - see @13@ - is mainly implemented in the @!Lib\mORMot.pas@ unit. Then it will use other units (like @!Lib\mORMotSQLite3.pas@, @!Lib\mORMotDB.pas@, @!Lib\SynSQLite3.pas@ or @!Lib\SynDB.pas@) to access to the various database back-ends.
Generic access to the data is implemented by defining high-level objects as {\i Delphi} classes, descendant from a main {\f1\fs20 @**TSQLRecord@} class.
In our @*Client-Server@ ORM, those {\f1\fs20 TSQLRecord} classes can be used for at least three main purposes:
- To store and retrieve data from any database engine - for most common usage, you can forget about writing @*SQL@ queries: @*CRUD@ data access statements ({\f1\fs20 SELECT / INSERT / UPDATE /DELETE}) are all created on the fly by the {\i Object-relational mapping} (ORM) core of {\i mORMot} - see @27@ - a @*NoSQL@ engine like {\i @*MongoDB@} can even be accessed the same way - see @84@;
- To have business logic objects accessible for both the Client and Server side, in a @*REST@ful approach - see @114@;
................................................................................
!begin
!  I := Props.Execute('select * from Sales.Customer where AccountNumber like ?',['AW000001%']);
!  while I.Step do
!    assert(Copy(I['AccountNumber'],1,8)='AW000001');
!end;
In this procedure, no {\f1\fs20 TSQLDBStatement} is defined, and there is no need to add a {\f1\fs20 try ... finally Query.Free; end;} block.
In fact, the {\f1\fs20 MyConnProps.Execute} method returns a {\f1\fs20 TSQLDBStatement} instance as a {\f1\fs20 ISQLDBRows}, which methods can be used to loop for each result row, and retrieve individual column values. In the code above, {\f1\fs20 I['FirstName']} will in fact call the {\f1\fs20 I.Column[]} default property, which will return the column value as a {\f1\fs20 variant}. You have other dedicated methods, like {\f1\fs20 ColumnUTF8} or {\f1\fs20 ColumnInt}, able to retrieve directly the expected data.
Note that all bound parameters will appear within the SQL statement, when @*log@ged using our {\f1\fs20 TSynLog} classes - see @73@.
:136  Late-binding
We implemented @*late-binding@ access of column values, via a custom variant time. It uses the internal mechanism used for {\i Ole Automation}, here to access column content as if column names where native object properties.
The resulting {\i Delphi} code to write is just clear and obvious:
!procedure UseProps(Props: TSQLDBConnectionProperties);
!var Row: Variant;
!begin
!  with Props.Execute('select * from Sales.Customer where AccountNumber like ?',
................................................................................
- {\i Not to cache unless you need to} (see Knuth's wisdom);
- {\i Ensure that caching is worth it} (if a value is likely to be overridden often, it could be even slower to cache it);
- Test once, test twice, always test and do not forget to test even more.
In practice, caching issues could be difficult to track. So in case of doubt (why was this data not accurate? it sounds like an old revision?), you may immediately disable caching, then ensure that you were not too optimistic about your cache policy.
:  What to cache
Typical content of these two tuned caches can be any global configuration settings, or any other kind of unchanging data which is not likely to vary often, and is accessed simultaneously by multiple clients, such as catalog information for an on-line retailer.
Another good use of caching is to store data that changes but is accessed by only one client at a time. By setting a cache at the client level for such content, the server won't be called often to retrieve the client-specific data. In such case, the problem of handling concurrent access to the cached data doesn't arise.
Profiling can be necessary to identify which data is to be registered within those caches, either at the client and/or the server side. The logging feature - see @73@ - integrated to {\i mORMot} can be very handy to tune the caching settings, due to its unique customer-side profiling ability.
But most of the time, an human guess at the business logic level is enough to set which data is to be cached on each side, and ensure content coherency.
:  How to cache
A tuned caching mechanism can be defined, for both {\f1\fs20 TSQLRestClient} and {\f1\fs20 TSQLRestServer} classes, at ID level.
By default, REST level cache is disabled, until you call {\f1\fs20 TSQLRest.Cache}'s {\f1\fs20 SetCache()} and {\f1\fs20 SetTimeOut()} methods. Those methods will define the caching policy, able to specify which table(s) or record(s) are to be cached, either at the client or the server level.
Once enabled for a table and a set of IDs on a given table, any further call to {\f1\fs20 TSQLRest.Retrieve(aClass,aID)} or {\f1\fs20 TSQLRecord.Create(aRest,aID)} will first attempt to retrieve the {\f1\fs20 TSQLRecord} of the given {\f1\fs20 aID} from the internal {\f1\fs20 TSQLRestCache} instance's in-memory cache, if available.\line Note that more complex requests, like queries on other fields than the ID @*primary key@, or JOINed queries, won't be cached at REST level. But such requests may benefit of the @37@, at {\i SQLite3} level, on the server side.
For instance, here is how the Client-side caching is tested about one individual record:
!    (...)
................................................................................
If you are using a {\i mORMot} server for the first time, you may be amazed by how most common process would sound just immediate. You can capitalize on the framework optimizations, which are able to unleash the computing power of your hardware, then refine your code only when performance matters.
In order to speed up our data processing, we first have to consider the classic architecture of a {\i mORMot} application - see @%%mORMotDesign3@.\line A {\i mORMot} client would have two means of accessing its data:
- Either from CRUD / ORM methods;
- Or via services, most probably {\i interface-based services} - see @63@.
Our optimization goals would therefore be leaded into those two directions.
:  Profiling your application
If you worry about performance, first reflex may be to enable the framework logging, even on customer sites.
The profiling abilities of the {\f1\fs20 TSynLog} class, used everywhere in our framework, will allow you to identify the potential bottlenecks of your client applications. See @16@ about this feature.\line From our experiment, we can assure you that the first time you may run logging on a real {\i mORMot} application, you may find issues you would never have think off by yourself.
Fight against any duplicated queries, unnecessary returned information (do not forget to specify the fields to be retrieved to your {\f1\fs20 CreateAndFillPrepare()} request), unattended requests triggered by the User Interface (typically a {\f1\fs20 TEdit}'s {\f1\fs20 OnChange} event may benefit of using a {\f1\fs20 TTimer} before asking for auto-completion)...
Once you have done this cleaning, you may be proud of you, and it may be enough for your customers to enjoy your application. You deserve a coffee or a drink.
:  Client-side caching
You may have written most of your business logic on the client side, and use CRUD / ORM methods to retrieve the information from your {\i mORMot} server.\line This is perfectly valid, but may be slow, especially if a lot of individual requests are performed over the network, which may be with high latency (e.g. over the Internet).
A new step of optimization, once you did identify your application bottlenecks via profiling, may be to tune your ORM client cache. In fact, there are several layers of caching available in a {\i mORMot} application. See @39@ for details about these features.\line Marking some tables as potentially cached on the client side may induce a noticeable performance boost, with no need of changing your client code.
:  Write business logic as Services
A further step of optimization may be to let the business logic be processed on the server side, within a @*service@.\line In fact, you would then switch your mind from a classic @7@ to a @17@.
................................................................................
- Remote @*CRUD@ operations, via @*JSON@ and @*REST@, with a {\f1\fs20 TSQLRestClientURI} class, with the same methods as with the {\f1\fs20 mORMot.pas} framework unit;
- Optimized {\f1\fs20 TSQLTableJSON} class to handle a JSON result table, as returned by {\i mORMot}'s REST server ORM - see @87@;
- @*Batch@ process - see @28@ - for transactional and high-speed writes;
- @49@ with parameters marshaling;
- @63@ with parameters marshaling and instance-life time;
- Mapping of most supported field types, including e.g. @*ISO 8601@ date/time encoding, @*BLOB@s and {\f1\fs20 TModTime}/{\f1\fs20 TCreateTime} - see @26@;
- Complex {\f1\fs20 record} types are also exported and consumed via JSON, on all platforms (for both ORM and SOA methods);
- Integrated debugging methods, used by both ORM and SOA process, able to log into a local file or to a remote server - see @103@;
- Some cross-platform low-level functions and types definitions, to help share as much code as possible between your projects.
In the future, C# or Java clients may be written.\line The {\f1\fs20 CrossPlatform} sub-folder code could be used as reference, to write minimal and efficient clients on any platform. Our REST model is pretty straightforward and standard, and use of JSON tends to leverage a lot of potential marshaling issues which may occur with XML or binary formats.
In practice, a code generator embedded in the {\i mORMot} server can be used to create the client wrappers, using the @81@ included on the server side. With a click, you can generate and download a client source file for any supported platform. A set of {\f1\fs20 .mustache} templates is available, and can be customized or extended to support any new platform: any help is welcome, especially for targeting Java or C# clients.
\page
: Available client platforms
:69  Delphi FMX / FreePascal FCL cross-platform support
Latest versions of {\i Delphi} include the {\i @**FireMonkey@} (FMX) framework, able to deliver multi-device, true native applications for {\i Windows}, {\i Mac @*OSX@}, {\i @*Android@} and {\i iOS} ({\i @*iPhone@}/{\i @*iPad@}).\line Our {\f1\fs20 SynCrossPlatform*} units are able to easily create clients for those platforms.
................................................................................
$xcopy SynCrossPlatformSpecific.pas "c:\ProgramData\Optimale Systemer AS\Smart Mobile Studio\Libraries" /Y
$xcopy SynCrossPlatformCrypto.pas "c:\ProgramData\Optimale Systemer AS\Smart Mobile Studio\Libraries" /Y
$xcopy SynCrossPlatformREST.pas "c:\ProgramData\Optimale Systemer AS\Smart Mobile Studio\Libraries" /Y
You can find a corresponding BATCH file in the {\f1\fs20 CrossPlatform} folder, and in {\f1\fs20 SQLite3\\Samples\\29 - SmartMobileStudio Client\\CopySynCrossPlatformUnits.bat}.
In fact, the {\f1\fs20 SynCrossPlatformJSON.pas} unit is not used under {\i Smart Mobile Studio}: we use the built-in JSON serialization features of {\i JavaScript}, using {\f1\fs20 variant} dynamic type, and the standard {\f1\fs20 JSON.Stringify()} and {\f1\fs20 JSON.Parse()} functions.
:  Remote logging
Since there is no true file system API available under a HTML5 sand-boxed application, logging to a local file is not an option. Even when packaged with {\i PhoneGap}, local log files are not convenient to work with.
Generated logs will have the same methods and format as with {\i Delphi} or {\i FreePascal} - see @105@. {\f1\fs20 TSQLRest.Log(E: Exception)} method will also log the stack trace of the exception!  Our {\f1\fs20 LogView} tool - see @103@ - is able to run as a simple but efficient remote log server and viewer, shared with regular or cross-platform units of the framework.
A dedicated {\i asynchronous} implementation has been refined for {\i Smart Mobile Studio} clients, so that several events would be gathered and sent at once to the remote server, to maximize bandwidth use and let the application be still responsive.\line It allows even complex mobile applications to be debugged with ease, on any device, even over WiFi or 3G/4G networks. Your support could ask your customer to enable logging for a particular case, then see in real time what is wrong with your application.
\page
: Generating client wrappers
Even if it is feasible to write the client code by hand, your {\i mORMot} server is able to create the source code needed for client access, via a dedicated method-based service, and a set of {\i @*Mustache@}-based templates - see @81@.
The following templates are available in the {\f1\fs20 CrossPlatform\\templates} folder:
|%45%45
|\b Unit Name|Compiler Target\b0
................................................................................
\Domain¤REST Server\ORM
\Domain¤Services\ORM
\
In order to provide the better scaling of the server side, @*cache@ can be easily implemented at every level, and hosting can be tuned in order to provide the best response time possible: one central server, several dedicated servers for application, domain and persistence layers...
Due to the @*SOLID@ design of {\i mORMot} - see @47@ - you can use as many Client-Server services layers as needed in the same architecture (i.e. a Server can be a Client of other processes), in order to fit your project needs, and let it evolve from the simplest architecture to a full scalable {\i Domain-Driven} design.
:12Testing and logging
%cartoon05.png
Since we covered most architectural and technical aspects of the framework, it is time to put the last missing bricks to the building, meaning testing and logging.
\page
: Automated testing
You know that @**test@ing is (almost) everything if you want to avoid regression problems in your application.
How can you be confident that any change made to your software code won't create any error in other part of the software?
Automated unit testing is a good candidate for avoiding any serious regression.
And even better, testing-driven coding can be encouraged:
- Write a void implementation of a feature, that is code the interface with no implementation;
- Write a test code;
................................................................................
- Launch the test - it must fail;
- Implement the feature;
- Launch the test - it must pass;
- Add some features, and repeat all previous tests every time you add a new feature.
It could sounds like a waste of time, but such coding improve your code quality a lot, and, at least, it help you write and optimize every implementation feature.
The framework has been implemented using this approach, and provide all the tools to write tests. In addition to what other {\i Delphi} frameworks offer (e.g. {\i DUnit / DUnitX}), it is very much integrated with other elements of the framework (like logging), and provide a complete {\i stubbing / mocking} mechanism to cover @62@.
:  Involved classes in Unitary testing
The @!TSynTest,TSynTestCase,TSynTests!Lib\SynTest.pas@ unit defines two classes (both inheriting from {\f1\fs20 TSynTest}), implementing a complete Unitary testing mechanism similar to {\i DUnit}, with less code overhead, and direct interface with the framework units and requirements (@*UTF-8@ ready, code compilation from {\i Delphi} 6 up to XE7 and FPC, no external dependency).
The following diagram defines this class hierarchy:
\graph HierTSynTest TSynTest classes hierarchy
\TSynTests\TSynTest
\TSynTestCase\TSynTest
\
The main usable class types are:
- {\f1\fs20 TSynTestCase}, which is a class implementing a test case: individual tests are written in the published methods of this class;
................................................................................
$Tests performed at 25/03/2014 10:59:33
$
$Total assertions failed for all test suits:  0 / 4,000
$! All tests passed successfully.
You can see that all text on screen was created by "UnCamelCasing" the method names (thanks to our good old @*Camel@), and that the test suit just follows the order defined when registering the classes. Each method has its own timing, which is pretty convenient to track performance regressions.
This test program has been uploaded in the {\f1\fs20 SQLite3\\Sample\\07 - SynTest} folder of the Source Code Repository.
:  Implemented tests
The @SAD-DI-2.2.2@ defines all classes released with the framework source code, which covers all core aspects of the framework. Global testing coverage is good, excellent for core components (more than 175,000,000 individual checks are performed for revision 1.18), but there is still some User-Interface related tests to be written.
Before any release all unitary regression tests are performed with the following compilers:
- {\i Delphi} 5 (for a limited scope, including {\f1\fs20 SynCommons.pas}, {\f1\fs20 SynLog.pas}, {\f1\fs20 SynTests.pas}, {\f1\fs20 SynPdf.pas} and {\f1\fs20 SynDB.pas});
- {\i Delphi} 6;
- {\i Delphi} 7, with and without our Enhanced Run Time Library;
- {\i Delphi} 2007;
- {\i Delphi} 2010 (we assume that if it works with {\i Delphi} 2010, it will work with {\i Delphi} 2009, with the exception of {\f1\fs20 generic} compilation);
- {\i Delphi} XE4;
- {\i Delphi} XE6;
- {\i Delphi} XE7;
- {\i FPC} 2.7.1 (svn revision).
Then all sample source code (including the {\i Main Demo} and {\f1\fs20 @*SynDBExplorer@} sophisticated tools) are compiled, and user-level testing is performed against those applications.
You can find in the {\f1\fs20 compil.bat} and {\f1\fs20 compilpil.bat} files of our source code repository how incremental builds and tests are performed.
\page
:16 Enhanced logging
A @**log@ging mechanism is integrated with cross-cutting features of the framework. It includes stack trace exception and such, just like {\i MadExcept}, using {\f1\fs20 .map} file content to retrieve debugging information from the source code.
Here are some of its features:
- Logging with a {\i set} of levels, not only a level scale;
- Fast, low execution overhead;
- Can load {\f1\fs20 .map} file symbols to be displayed in logging (i.e. source code file name and line numbers are logged instead of a hexadecimal value);
- Compression of {\f1\fs20 .map} into binary {\f1\fs20 .mab} (900 KB -> 70 KB);
- Inclusion of the {\f1\fs20 .map/.mab} into the {\f1\fs20 .exe}, with very slow size increase;
- Exception logging ({\i Delphi} or low-level exceptions) with unit names and line numbers;
- Optional stack trace with units and line numbers;
- Methods or procedure recursive tracing, with {\i Enter} and {\i auto-Leave} (using a fake {\f1\fs20 interface} instance);
- High resolution time stamps, for customer-side profiling of the application execution;
- Set / enumerates / {\f1\fs20 TList} / {\f1\fs20 TPersistent} / {\f1\fs20 TObjectList} / dynamic array JSON serialization;
- Per-thread or global logging;
- Optional multiple log files on the same process;
- Optional rotation when main log reaches a specified size, with compression of the rotated logs;
- Integrated log archival (in {\f1\fs20 .zip} or any other format, including our {\f1\fs20 .synlz});
- Optional colored echo to a console window, for interactive debugging;
- Fast log viewer tool available, including thread filtering and customer-side execution profiling;
- Optional remote logging via HTTP - the log viewer can be used as server.
:  Setup logging
Logging is defined mainly by a per-class approach. You usually define your logging expectations by using a {\f1\fs20 TSynLog} class, and setting its {\f1\fs20 Family} property. Note that it is perfectly feasible to use you own {\f1\fs20 TSynLog} class instance, with its own {\f1\fs20 TSynLog} family settings, injected at the {\f1\fs20 constructor} level; but in {\i mORMot}, we usually use the per-class approach, via {\f1\fs20 TSynLog}, {\f1\fs20 TSQLLog}, {\f1\fs20 SynDBLog} and {\f1\fs20 SQLite3Log} - see @73@.
For sample code (and the associated log viewer tool), see "{\i 11 - Exception logging}" folder in "{\i Sqlite3\\Samples}".
In short, you can add logging to your program, just by using the {\f1\fs20 TSynLog} class, as such:
!  TSynLog.Add.Log(sllInfo,Stats.DebugMessage);
This line will log the {\f1\fs20 Stats.DebugMessage} text, with a {\f1\fs20 sllInfo} notification level. See the description of all {\f1\fs20 Log()} overloaded methods of the {\f1\fs20 ISynLog} interface, to find out how your project can easily log events.
First of all, you need to define your logging setup via code:
!  with TSynLog.Family do begin
!    Level := LOG_VERBOSE;
!    //Level := [sllException,sllExceptionOS];
!    //HighResolutionTimeStamp := true;
!    //AutoFlushTimeOut := 5;
!    OnArchive := EventArchiveSynLZ;
!    //OnArchive := EventArchiveZip;
!    ArchiveAfterDays := 1; // archive after one day
!  end;
The main setting here is {\f1\fs20 TSynLog.Family.Level := ...} which defines which levels are to be logged. That is, if {\f1\fs20 sllInfo} is part of {\f1\fs20 TSynLog.Family.Level}, any {\f1\fs20 TSynLog.Add.Log(sllInfo,...)} command will log the corresponding content - otherwise, it will be a no-operation. {\f1\fs20 LOG_VERBOSE} is a constant setting all levels at once.
You have several debugging levels available, and even 4 custom types:
!  TSynLogInfo = (
!    sllNone, sllInfo, sllDebug, sllTrace, sllWarning, sllError,
!    sllEnter, sllLeave,
!    sllLastError, sllException, sllExceptionOS, sllMemory, sllStackTrace,
!    sllFail, sllSQL, sllCache, sllResult, sllDB, sllHTTP, sllClient, sllServer,
!    sllServiceCall, sllServiceReturn, sllUserAuth,
!    sllCustom1, sllCustom2, sllCustom3, sllCustom4, sllNewRun);
Here are the purpose of each logging level:
- {\f1\fs20 sllInfo} will log general information events;
- {\f1\fs20 sllDebug} will log detailed debugging information;
- {\f1\fs20 sllTrace} will log low-level step by step debugging information;
- {\f1\fs20 sllWarning} will log unexpected values (not an error);
- {\f1\fs20 sllError} will log errors;
- {\f1\fs20 sllEnter} will log every method start;
- {\f1\fs20 sllLeave} will log every method quit;
- {\f1\fs20 sllLastError} will log the {\f1\fs20 GetLastError} OS message;
- {\f1\fs20 sllException} will log all exception raised - available since Windows XP;
- {\f1\fs20 sllExceptionOS} will log all OS low-level exceptions ({\f1\fs20 EDivByZero, ERangeError, EAccessViolation}...);
- {\f1\fs20 sllMemory} will log memory statistics;
- {\f1\fs20 sllStackTrace} will log caller's stack trace (it is by default part of {\f1\fs20 TSynLogFamily. LevelStackTrace} like {\f1\fs20 sllError, sllException, sllExceptionOS, sllLastError} and {\f1\fs20 sllFail});
- {\f1\fs20 sllFail} was defined for {\f1\fs20 TSynTestsLogged. Failed} method, and can be used to log some customer-side assertions (may be notifications, not errors);
- {\f1\fs20 sllSQL} is dedicated to trace the SQL statements;
- {\f1\fs20 sllCache} should be used to trace any internal caching mechanism (it is used for instance by our SQL statement caching);
- {\f1\fs20 sllResult} could trace the SQL results, JSON encoded;
- {\f1\fs20 sllDB} is dedicated to trace low-level database engine features;
- {\f1\fs20 sllHTTP} could be used to trace HTTP process;
- {\f1\fs20 sllClient/sllServer} could be used to trace some Client or Server process;
- {\f1\fs20 sllServiceCall/sllServiceReturn} to trace some remote service or library;
- {\f1\fs20 sllUserAuth} to trace user authentication (e.g. for individual requests);
- {\f1\fs20 sllCustom1..sllCustom4} items can be used for any purpose by your programs;
- {\f1\fs20 sllNewRun} will be written when a process re-opens a rotated log.
Logging is not using directly a {\f1\fs20 TSynLogInfo} level, but the following {\f1\fs20 set}:
!  /// used to define a logging level
!  // - i.e. a combination of none or several logging event
!  // - e.g. use LOG_VERBOSE constant to log all events
!  TSynLogInfos = set of TSynLogInfo;
Most logging tools in the wild use a level scale, i.e. with a hierarchy, excluding the lower levels when one is selected.
Our logging classes use a {\i set}, and not directly a particular level, so you are able to select which exact events are worth recording. In practice, we found this pattern to make a lot of sense and to be much more efficient for support.
:  Call trace
The logging mechanism can be used to trace recursive calls. It can use an interface-based mechanism to log when you enter and leave any method:
!procedure TMyDB.SQLExecute(const SQL: RawUTF8);
!var ILog: ISynLog;
!begin
!  ILog := TSynLogDB.Enter(self,'SQLExecute');
!  // do some stuff
!  ILog.Log(sllInfo,'SQL=%',[SQL]);
!end; // when you leave the method, it will write the corresponding event to the log
It will be logged as such:
$20110325 19325801  +    MyDBUnit.TMyDB(004E11F4).SQLExecute
$20110325 19325801 info   SQL=SELECT * FROM Table;
$20110325 19325801  -
Note that by default you have human-readable {\i time and date} written to the log, but it is also possible to replace this timing with {\i high-resolution timestamps}. With this, you'll be able to profile your application with data coming from the customer side, on its real computer. Via the {\f1\fs20 Enter} method (and its {\i auto-Leave} feature), you have all information needed for this.
:  Including symbol definitions
In the above logging content, the method name is set in the code (as {\f1\fs20 'SQLExecute'}). But if the logger class is able to find a {\f1\fs20 .map} file associated to the {\f1\fs20 .exe}, the logging mechanism is able to read this symbol information, and write the exact line number of the event.
By default, the {\f1\fs20 .map} file information is not generated by the compiler. To force its creation, you must ensure the {\f1\fs20 \{$D+\}} compiler directive is set in every unit (which is the case by default, unless you set {\f1\fs20 \{$D-\}} in the source), and the "{\i Detailed Map File}" option selected in the {\i Project > Options > Linker} page of the {\i Delphi} IDE.
In the following log entries, you'll see both high-resolution time stamp, and the entering and leaving of a {\f1\fs20 TTestCompression.TestLog} method traced with no additional code (with accurate line numbers, extracted from the {\f1\fs20 .map} content):
$0000000000000B56  +    TTestCompression(00AB3570).000E6C79 SynSelfTests.TTestCompression.TestLog (376)
$0000000000001785  -
There is already a dedicated {\f1\fs20 TSynLogFile} class able to read the {\f1\fs20 .log} file, and recognize its content.
The first time the {\f1\fs20 .map} file is read, a {\f1\fs20 .mab} file is created, and will contain all symbol information needed. You can send the {\f1\fs20 .mab} file with the {\f1\fs20 .exe} to your client, or even embed its content to the {\f1\fs20 .exe} (see the {\f1\fs20 Map2Mab.dpr} sample file located in the {\f1\fs20 Samples\\11 - Exception logging\\} folder).
This {\f1\fs20 .mab} file is very optimized: for instance, a {\f1\fs20 .map} of 927,984 bytes compresses into a 71,943 {\f1\fs20 .mab} file.
:  Exception handling
Of course, this @*log@ging mechanism is able to intercept the raise of exceptions, including the worse (e.g. {\f1\fs20 EAccessViolation}), to be logged automatically in the log file, as such:
$000000000000090B EXCOS EAccessViolation (C0000005) at 000E9C7A SynSelfTests.Proc1 (785)  stack trace 000E9D51 SynSelfTests.Proc2 (801) 000E9CC1 SynSelfTests.Proc1 (790) 000E9D51 SynSelfTests.Proc2 (801) 000E9CC1 SynSelfTests.Proc1 (790) 000E9D51 SynSelfTests.Proc2 (801) 000E9CC1 SynSelfTests.Proc1 (790) 000E9D51 SynSelfTests.Proc2 (801) 000E9CC1 SynSelfTests.Proc1 (790) 000E9D51 SynSelfTests.Proc2 (801) 000E9CC1 SynSelfTests.Proc1 (790) 000E9E2E SynSelfTests.TestsLog (818) 000EA0FB SynSelfTests (853) 00003BF4 System.InitUnits 00003C5B System.@StartExe 000064AB SysInit.@InitExe 000EA3EC TestSQL3 (153)
The {\f1\fs20 TSynLogInfo} logging level makes a difference between high-level {\i Delphi} exceptions ({\f1\fs20 sllException}) and lowest-level OS exceptions ({\f1\fs20 sllExceptionOS}) like {\f1\fs20 EAccessViolation}.
For instance, if you add to your program:
!uses
!  SynLog;
!(...)
!  TSynLog.Family.Level := [sllExceptionOS];
all OS exceptions (excluding pure {\i Delphi} exception like {\f1\fs20 EConvertError} and such) will be logged to a separated log file.
!TSynLog.Family.Level := [sllException,sllExceptionOS];
will trace also {\i Delphi} exceptions, for instance.
You can specify some {\f1\fs20 Exception} class to be ignored, by adding them to {\f1\fs20 Family.ExceptionIgnore} internal list. It could make sense to add this setting, if your code often triggers some non-breaking exceptions, e.g. with {\f1\fs20 StrToInt()}:
!  TSynLog.Family.ExceptionIgnore.Add(EConvertError);
If your {\i Delphi} code executes some {\i .Net} managed code (e.g. exposed via some COM wrapper components), the unit is able to recognize most un-handled {\i .Net} exceptions, and log them with their original {\f1\fs20 C#} class name (for instance, {\f1\fs20 EOleSysError 80004003} will be recorded as a much more user-friendly "{\f1\fs20 [.NET/CLR unhandled ArgumentNullException]}" message.
You can set the following global variable to assign a customized callback, and be able to customize the logging content associated to any exception:
!type
!  /// global hook callback to customize exceptions logged by TSynLog
!  // - should return FALSE if Context.EAddr and Stack trace is to be appended
!  TSynLogExceptionToStr = function(WR: TTextWriter; const Context: TSynLogExceptionContext): boolean;
!
!var
!  /// allow to customize the Exception logging message
!  TSynLogExceptionToStrCustom: TSynLogExceptionToStr = nil;
The {\f1\fs20 Context: TSynLogExceptionContext} content is to be used to append some text to the specified {\f1\fs20 TTextWriter} instance.
An easier possibility is to inherit your custom exception class from {\f1\fs20 ESynException}, and override its unique virtual method:
!  /// generic parent class of all custom Exception types of this unit
!  ESynException = class(Exception)
!  public
!    /// can be used to customize how the exception is logged
!    // - this default implementation will call the DefaultSynLogExceptionToStr()
!    // callback or TSynLogExceptionToStrCustom, if defined
!    // - override this method to provide a custom logging content
!    // - should return TRUE if Context.EAddr and Stack trace is not to be
!    // written (i.e. as for any TSynLogExceptionToStr callback)
!!    function CustomLog(WR: TTextWriter; const Context: TSynLogExceptionContext): boolean; virtual;
!  end;
See {\f1\fs20 TSynLogExceptionContext} to check the execution context, and the implementation of the {\f1\fs20 function DefaultSynLogExceptionToStr()} function.
:  Serialization
{\i @*dynamic array@s} can also be serialized as @*JSON@ in the log on request, via the default {\f1\fs20 TSynLog} class, as defined in {\f1\fs20 SynLog.pas} unit - see @48@.
The {\f1\fs20 TSQLLog} class (using the enhanced @*RTTI@ methods defined in {\f1\fs20 mORMot.pas} unit) is even able to serialize {\f1\fs20 @*TSQLRecord@, @*TPersistent@, TList} and {\f1\fs20 @*TCollection@} instances as JSON, or any other class instance, after call to {\f1\fs20 TJSONSerializer. @*RegisterCustomSerializer@}.
For instance, the following code:
!procedure TestPeopleProc;
!var People: TSQLRecordPeople;
!    Log: ISynLog;
!begin
!  Log := TSQLLog.Enter;
!  People := TSQLRecordPeople.Create;
!  try
!    People.ID := 16;
!    People.FirstName := 'Louis';
!    People.LastName := 'Croivébaton';
!    People.YearOfBirth := 1754;
!    People.YearOfDeath := 1793;
!    Log.Log(sllInfo,People);
!  finally
!    People.Free;
!  end;
!end;
will result in the following log content:
$0000000000001172  +    000E9F67 SynSelfTests.TestPeopleProc (784)
$000000000000171B info      {"TSQLRecordPeople(00AB92E0)":{"ID":16,"FirstName":"Louis","LastName":"Croivébaton","Data":"","YearOfBirth":1754,"YearOfDeath":1793}}
$0000000000001731  -
:  Multi-threaded applications
You can define several @*log@ files per process, and even a per-thread log file, if needed (it could be sometimes handy, for instance on a server running the same logic in parallel in several threads).
The logging settings are made at the logging class level. Each logging class (inheriting from {\f1\fs20 TSynLog}) has its own {\f1\fs20 TSynLogFamily} instance, which is to be used to customize the logging class level. Then you can have several instances of the individual {\f1\fs20 TSynLog} classes, each class sharing the settings of the {\f1\fs20 TSynLogFamily}.
You can therefore initialize the "family" settings before using logging, like in this code which will force to log all levels ({\f1\fs20 LOG_VERBOSE}), and create a per-thread log file, and write the {\f1\fs20 .log} content not in the {\f1\fs20 .exe} folder, but in a custom directory:
! with TSynLogDB.Family do
! begin
!   Level := LOG_VERBOSE;
!!   PerThreadLog := ptOneFilePerThread;
!   DestinationPath := 'C:\Logs';
! end;
If you specifies {\f1\fs20 PerThreadLog := ptIdentifiedInOnFile} for the family, a new column will be added for each log row, with the corresponding {\f1\fs20 ThreadID} - the supplied {\f1\fs20 LogView} tool will handle it as expected. This can be very useful for a multi-threaded server process, e.g. as implement with {\i mORMot}'s Client-Server classes @35@.
:  Log to the console
For debugging purposes, it could be very handy to output the logging content to a console window. It enables interactive debugging of a Client-Server process, for instance: you can interact with the Client, then look in real time at the server console window, and inspect which requests are processed, without the need to open the log file.
The {\f1\fs20 EchoToConsole} property enable you to select which events are to be echoed on the console (perhaps you expect only errors to appear, for instance).
!  with TSQLLog.Family do begin
!    Level := LOG_VERBOSE;
!!    EchoToConsole := LOG_VERBOSE; // log all events to the console
!  end;
Depending on the events, colors will be used to write the corresponding information. Errors will be displayed as light red, for instance.
Note that this echoing process slow down the logging process a lot, since it is currently implemented in a blocking mode, and writing to the console under {\i Windows} is much slower than writing to a file. This feature is therefore disabled by default, and not to be enabled on a production server, but only to make interactive debugging easier.
:104  Remote logging
By default, {\f1\fs20 TSynLog} writes its activity to a local file, and/or to the console. The log file can be transmitted later on (once compressed) to support, for further review and debugging.\line But sometimes, it may be handy to see the logging in real-time, on a remote computer.
You can enable such remote monitoring for a given {\f1\fs20 TSynLog} class, by adding the {\f1\fs20 mORMotHTTPClient.pas} unit in your use clause, then calling the following constructor:
! TSQLHttpClient.CreateForRemoteLogging('192.168.1.15',SQLite3Log,'8091','LogService');
This command will let any {\f1\fs20 SQLite3Log} event be sent to a remote server running at {\f1\fs20 http://192.168.1.15:8091/LogService/RemoteLog} - in fact this should be a {\i mORMot} server, but may be any REST server, able to answer to a {\f1\fs20 PUT} command sent to this URI.
A {\f1\fs20 TSQLHttpClient} instance will be created, and will be managed by the {\f1\fs20 SQLite3Log} instance. It will be released when the application will be closed, or when the {\f1\fs20 SQLite3Log.Family.EchoRemoteStop} method will be called.
In practice, our {\i Log View} tool - see @103@ - is able to run as a compatible remote server. Execute the tool, set the expected {\i Server Root} name ('{\f1\fs20 LogService}' by default), and the expected {\i Server Port} (8091 by default), then click on the "{\f1\fs20 Server Launch}" button.\line The {\i Log View} tool will now display in real time all incoming events, search into their content, and allow to save all received events into a regular {\f1\fs20 .log} or {\f1\fs20 .synlz} file, for further archiving and study.\line Note that since the {\i Log View} tool will run a {\f1\fs20 http.sys} based server - see @88@ - you may have to run once the tool with administrator rights, to register the {\i Server Root} / {\i Server Port} combination for binding.
Implementation of this remote logging has been tuned on both client and server side.\line On client side, log events are gathered and sent in a dedicated background thread: if a lot of events are generated, they will be transferred in chunks of several rows, to minimize resource and bandwidth. On server side, incoming events are stored in memory, and indexed on the fly, with a periodic refresh rate of 500 ms: even a very active client logger would just let the {\i Log View} tool be responsive and efficient.\line Thanks to the nature of the {\f1\fs20 http.sys} based server, several {\i Server Root} URI can be accessed in parallel with several {\i Log View} tool instance, on the same HTTP port: it will ease the IT policy of your network, since a single forwarded port would be able to handle several incoming connections.
See the "{\f1\fs20 RemoteLoggingTest.dpr}" sample from "{\f1\fs20 11 - Exception logging}", in conjunction with the {\f1\fs20 LogView.dpr} tool available in the same folder, for a running example of remote logging.
Note that our cross-platform clients - see @86@ - are able to log to a remote server, with the same exact format as used by our {\f1\fs20 TSynLog} class.
:  Log to third-party libraries
Our {\f1\fs20 TSynLog} class was designed to write its information to a file, and optionally to the console or a remote log server (as we just saw). In fact, {\f1\fs20 TSynLog} is extensively used by the {\i mORMot} framework to provide various levels of details on what happens behind the scene: it is great for debugging purposes.
It may be convenient to let {\f1\fs20 TSynLog} work with any third party logging applications such as {\i CodeSite} or {\i SmartInspect}, or any proprietary solution. As a result, {\i mORMot} logs can be mixed with existing application logs.
You can define the {\f1\fs20 TSynLogFamily.EchoCustom} property to specify a simple event to be triggered for each log operation: the application can then decide to log to a third party logger application.
Note that there is also the {\f1\fs20 TSynLogFamily.NoFile} property, which allows to disable completely the built-in file logging mechanism.
For instance, you may write:
!procedure TMyClass.Echo(Sender: TTextWriter; Level: TSynLogInfo; const Text: RawUTF8);
!begin
!  if Level in LOG_STACKTRACE then // filter only errors
!    writeln(Text); // could be any third-party logger
!end;
!
!...
!  with TSQLLog.Family do begin
!    Level := LOG_VERBOSE;
!    // EchoToConsole := LOG_VERBOSE; // log all events to the console
!!    EchoCustom := aMyClass.Echo; // register third-party logger
!!    NoFile := true; // ensure TSynLog won't use the default log file
!  end;
A process similar to {\f1\fs20 TSynLogFile.ProcessOneLine()} could then parse the incoming {\f1\fs20 Text} value, if needed.
:  Automated log archival
Log archives can be created with the following settings:
! with TSynLogDB.Family do
! begin
!  (...)
!  OnArchive := EventArchiveZip;
!  ArchivePath := '\\Remote\WKS2302\Archive\Logs'; // or any path
! end;
The {\f1\fs20 ArchivePath} property can be set to several functions, taking a timeout delay from the {\f1\fs20 ArchiveAfterDays} property value:
- {\f1\fs20 nil} is the default value, and won't do anything: the {\f1\fs20 .log} will remain on disk until they will be deleted by hand;
- {\f1\fs20 EventArchiveDelete} in order to delete deprecated {\f1\fs20 .log} files;
- {\f1\fs20 EventArchiveSynLZ} to compress the {\f1\fs20 .log} file into a proprietary {\i @*SynLZ@} format: resulting file name will be located in {\f1\fs20 ArchivePath\\log\\YYYYMM\\*.log.synlz}, and the command-line {\f1\fs20 UnSynLz.exe} tool (calling {\f1\fs20 FileUnSynLZ} function of {\f1\fs20 SynCommons.pas} unit) can be used to uncompress it in to plain {\f1\fs20 .log} file;
- {\f1\fs20 SynZip.EventArchiveZip} will archive the {\f1\fs20 .log} files in {\f1\fs20 ArchivePath\\log\\YYYYMM.zip} files, grouping every .
{\i SynLZ} files are less compressed, but created much faster than {\f1\fs20 .zip} files. However, {\f1\fs20 .zip} files are more standard, and on a regular application, compression speed won't be an issue for the application.
:  Log files rotation
You can set {\f1\fs20 TSynLogFamily.RotateFileCount} and {\f1\fs20 RotateFileSizeKB} properties, to enable log file rotation:
- If both values are > 0, the log file will have a fixed name, without any time-stamp within;
- {\f1\fs20 RotateFileSizeKB} will define the maximum size of the main uncompressed log file
- {\f1\fs20 RotateFileCount} will define how many files are kept on disk - note that rotated files are compressed using {\i SynLZ}, so compression will be very fast.
Log file rotation is as easy as:
!  with TSQLLog.Family do begin
!    Level := LOG_VERBOSE;
!    RotateFileCount := 5;        // will maintain a set of up to 5 files
!    RotateFileSizeKB := 20*1024; // rotate by 20 MB logs
!  end;
Such a logging definition will create those files on disk, e.g. for the {\f1\fs20 TestSQL3.dpr} regression tests:
- {\f1\fs20 TestSQL3.log} which will be the latest (current) log file, uncompressed;
- {\f1\fs20 TestSQL3.1.synlz} to {\f1\fs20 TestSQL3.4.synlz} will be the 4 latest log files, after compression. Our {\i Log Viewer} tool - see @103@ - is able to uncompress those {\f1\fs20 .synlz} files directly.
Note that as soon as you active file rotation, {\f1\fs20 PerThreadLog = ptOneFilePerThread} and {\f1\fs20 HighResolutionTimeStamp} properties will be ignored, since both features expect a single file to exist per {\f1\fs20 TSynLog} class.
As an alternative, or in addition to this {\i by-size} rotation pattern, you could specify a fixed time of the day to perform the rotation.\line For instance, the following will perform automatic rotation of the log files, whatever their size, at {\f1\fs20 23:00} each evening:
!  with TSQLLog.Family do begin
!    Level := LOG_VERBOSE;
!    RotateFileCount := 5;        // will maintain a set of up to 5 files
!    RotateFileDailyAtHour := 23; // rotate at 11:00 PM
!  end;
If the default behavior - which is to compress all rotated files into {\f1\fs20 .synlz} format, and delete the older files - does not fit your needs, you can set a custom event to the {\f1\fs20 TSynLogFamily.OnRotate} property, which would take care of the file rotation process.
:  Integration within tests
Logging is integrated within the unit @*test@ing classes, so that any failure will create an entry in the log with the source line, and stack trace:
$C:\Dev\lib\SQLite3\exe\TestSQL3.exe 0.0.0.0 (2011-04-13)
$Host=Laptop User=MyName CPU=2*0-15-1027 OS=2.3=5.1.2600 Wow64=0 Freq=3579545
$TSynLogTest 1.13 2011-04-13 05:40:25
$
$20110413 05402559 fail  TTestLowLevelCommon(00B31D70) Low level common: TDynArray "dynamic array failure" stack trace 0002FE0B SynCommons.TDynArray.Init (15148) 00036736 SynCommons.Test64K (18206) 0003682F SynCommons.TTestLowLevelCommon._TDynArray (18214) 000E9C94 TestSQL3 (163)
The difference between a test suit without logging ({\f1\fs20 TSynTests}) and a test suit with logging ({\f1\fs20 TSynTestsLogged}) is only this overridden method:
!procedure TSynTestsLogged.Failed(const msg: string; aTest: TSynTestCase);
!begin
!  inherited;
!  with TestCase[fCurrentMethod] do
!    fLogFile.Log(sllFail,'%: % "%"',
!      [Ident,TestName[fCurrentMethodIndex],msg],aTest);
!end;
In order to enable tests logging, you have just to enable it, e.g. with:
! TSynLogTestLog.Family.Level := LOG_VERBOSE;
You can optionally redirect the following global variable at program initialization, to share testing events with the main {\i mORMot} logs:
!  with TSQLLog.Family do begin
!    Level := LOG_VERBOSE;
!!    TSynLogTestLog := TSQLLog; // share the same log file with whole mORMot
!  end;
:103  Log Viewer
Since the log files tend to be huge (for instance, if you set the logging for our unitary tests, the 17,000,000 test cases do create a huge log file of about 550 MB), a log viewer was definitively in need.
The log-viewer application is available as source code in the "{\i Samples}" folder, in the "{\i 11 - Exception logging}" sub-folder.
:   Open log files
You can run it with a specified log file on the command line, or use the "{\i Browse}" button to browse for a file. That is, you can associate this tool with your {\f1\fs20 .log} files, for instance, and you'll open it just by double-clicking on such files.
Note that if the file is not in our {\f1\fs20 TSynLog} format, it will still be opened as plain text. You'll be able to browse its content and search within, but all the nice features of our logging won't be available, of course.
It is worth saying that the viewer was designed to be {\i fast}.\line In fact, it takes no time to open any log file. For instance, a 390 MB log file is opened in less than one second on my laptop. Under Windows Seven, it takes more time to display the "Open file" dialog window than reading and indexing the 390 MB content.\line It uses internally memory mapped files and optimized data structures to access to the data as fast as possible - see {\f1\fs20 TSynLogFile} class.
:   Log browser
The screen is divided into three main spaces:
- On the left side, the panel of commands;
- On the right side, the log events list;
- On the middle, an optional list of method calls, and another list of threads (not shown by default).
The command panel allows to {\i Browse} your disk for a {\f1\fs20 .log} file. This button is a toggle of an optional {\i Drive / Directory / File} panel on the leftmost side of the tool. When a {\f1\fs20 .log / .synlz / .txt} file is selected, its content is immediately displayed. You can specify a directory name as a parameter of the tool (e.g. in a {\f1\fs20 .lnk} desktop link), which will let the viewer be opened in "Browse" mode, starting with the specified folder.
A button gives access to the global {\i Stats} about its content (customer-side hardware and software running configuration, general numbers about the log), and even ask for a source code line number and unit name from a hexadecimal address available in the log, by browsing for the corresponding {\f1\fs20 .map} file (could be handy if you did not deliver the {\f1\fs20 .map} content within your main executable - which you should have to, IMHO).
Just below the "{\i Browse}" button, there is an edit field available, with a ? button. Enter any text within this edit field, and it will be searched within the log events list. Search is case-insensitive, and was designed to be fast. Clicking on the ? button (or pressing the {\f1\fs20 F3} key) allows to repeat the last search.
In the very same left panel, you can see all existing events, with its own color and an associated check-box. Note that only events really encountered in the {\f1\fs20 .log} file appear in this list, so its content will change between log files. By selecting / un-selecting a check-box, the corresponding events will be instantaneously displayed / or not on the right side list of events. You can right click on the events check-box list to select a predefined set of events.
The right colored event list follows the events appended to the log, by time order. When you click on an event, its full line content is displayed at the bottom on the screen, in a memo.
Having all @*SQL@ / @*NoSQL@ and @*Client-Server@ events traced in the log is definitively a huge benefit for customer support and bug tracking.
:   Customer-side profiler
One distinctive feature of the {\f1\fs20 TSynLog} logging class is that it is able to map methods or functions entering/leaving (using the {\f1\fs20 Enter} method), and trace this into the logs. The corresponding timing is also written within the "{\i Leave}" event, and allows application profiling from the customer side. Most of the time, profiling an application is done during the testing, with a test environment and database. But this is not, and will never reproduce the exact nature of the customer use: for instance, hardware is not the same (network, memory, CPU), nor the software (Operating System version, [anti-]virus installed)... By enabling customer-side method profiling, the log will contain all relevant information. Those events are named "{\i Enter}" / "{\i Leave}" in the command panel check-box list, and written as + and - in the right-sided event list.
The "{\i Methods profiler}" options allow to display the middle optional method calls list. Several sort order are available: by name (alphabetical sort), by occurrence (in running order, i.e. in the same order than in the event log), by time (the full time corresponding to this method, i.e. the time written within the "{\i Leave}" event), and by proper time (i.e. excluding all time spent in the nested methods).
The "{\i Merge method calls}" check-box allows to regroup all identical method calls, according to their name. In fact, most methods are not called once, but multiple time. And this is the accumulated time spent in the method which is the main argument for code profiling.
I'm quite sure that the first time you'll use this profiling feature on a huge existing application, you'll find out some bottlenecks you would have never thought about before.
:   Per-thread inspection
If the {\f1\fs20 TSynLog} family has specified {\f1\fs20 PerThreadLog := ptIdentifiedInOnFile} property, a new column will be added for each log row, with the corresponding {\f1\fs20 ThreadID} of the logged action.
The log-viewer application will identify this column, and show a "{\i Thread}" group below the left-side commands. It will allow to go to the next thread, or toggle the optional {\i Thread view} list. By checking / un-checking any thread of this list, you are able to inspect the execution log for a given process, very easily. A right-click on this thread list will display a pop-up menu, allowing to select all threads or no thread in one command.
:   Server for remote logging
As was stated above, @104@ can use our {\i Log View} tool as server and real-time viewer for any remote client, either using {\f1\fs20 TSynLog}, or any cross-platform client - see @86@.
Using a remote logging is specially useful from mobile applications (written with {\i Delphi} / @*FireMonkey@ or with @*Smart Mobile Studio@ / @*AJAX@). Our viewer tool allows efficient live debugging of such platforms.
:73  Framework log integration
The framework makes an extensive use of the logging features implemented in the {\f1\fs20 SynLog.pas} unit - see @16@.
In its current implementation, the framework is able to log on request:
- Any exceptions triggered during process, via {\f1\fs20 sllException} and {\f1\fs20 sllExceptionOS} levels;
- Client and server @*REST@ful {\f1\fs20 URL} methods via {\f1\fs20 sllClient} and {\f1\fs20 sllServer} levels;
- @*SQL@ executed statements in the {\i @*SQLite3@} engine via the {\f1\fs20 sllSQL} level;
- @*JSON@ results when retrieved from the {\i SQLite3} engine via the {\f1\fs20 sllResult} level;
- Main errors triggered during process via {\f1\fs20 sllError} level;
- @*Security@ User authentication and @*session@ management via {\f1\fs20 sllUserAuth};
................................................................................
The framework source code tree will compile and is tested for the following platforms:
- {\i Delphi} 6 up to {\i Delphi} XE7 compiler and IDE, with @*FPC@ 2.7.1 support;
- Server side on Windows 32 bit and @**64 bit@ platforms ({\i Delphi} XE2 and up is expected when targeting {\i Win64});
- Preliminary @*Linux@ platform for @*ORM@ servers using the FPC compiler - not yet stable enough to be used on production, and @*SOA@ or Web @*MVC@ not yet working due to a FPC bug - see @112@;
- VCL client on Win32/Win64 - GUI may be compiled optionally with third-party non Open-Source @*TMS@ Components, instead of default VCL components - see @http://www.tmssoftware.com/site/tmspack.asp
- @69@ clients on any supported platforms;
- @90@ startup with 2.1, for creating AJAX / HTML5 / Mobile clients.
Some part of the library (e.g. {\f1\fs20 SynCommons.pas}, {\f1\fs20 SynTest.pas}, {\f1\fs20 SynLog.pas} {\f1\fs20 SynPDF.pas} or the @27@ units) are also compatible with {\i Delphi} 5.
If you want to compile {\i mORMot} unit into @*packages@, to avoid an obfuscated {\i [DCC Error] @*E2201@ Need imported data reference ($G) to access 'VarCopyProc'} error at compilation, you should defined the {\f1\fs20 USEPACKAGES} conditional in your project's options. Open {\f1\fs20 SynCommons.inc} for a description of this conditional, and all over definitions global to all {\i mORMot} units - see @45@.
Note that the framework is expected to create only Windows server applications yet.\line But @86@ are available, using either {\i @*FireMonkey@} (FMX) library for User Interface generation, {\i @*FreePascal@ Compiler} (FPC) / {\i @*Lazarus@} support, or other tools more neutral, using @*JavaScript@ and @*AJAX@ via {\i @*Smart Mobile Studio@} - or both. The framework source code implementation and design tried to be as cross-platform as possible, since the beginning.
For HTML5 and Mobile clients, our main platform is {\i Smart Mobile Studio}, which is a great combination of ease of use, a powerful {\i SmartPascal} dialect, small applications (much smaller than FMX), with potential packaging as native iOS or {\i Android} applications (via {\i @*PhoneGap@}).
The latest versions of the {\i FreePascal Compiler} together with its great {\i Lazarus} IDE, are now very stable and easy to work with. I've tried for instance the {\i CodeTyphon} release (which is not the stable branch, but the latest version of both FPC and {\i Lazarus}) - see @http://www.pilotlogic.com - and found it to be impressive. This is amazing to build the whole set of compilers and IDE, with a lot of components, for several platforms (this is a cross-platform project), just from the sources. I like {\i Lazarus} stability and speed much more than {\i Delphi} (did you ever tried to browse and debug {\i included} {\f1\fs20 $I ...} files in the {\i Delphi} IDE? with Lazarus, it is painless), even if the compiler is slower than {\i Delphi}'s, and if the debugger is less integrated and even more unstable than {\i Delphi}'s under Windows (yes, it is possible!). At least, it works, and works pretty well. Official {\i @*Linux@} / {\i FPC} support is available for {\i mORMot} servers - thanks to Alfred! - but this platform is brand new to the framework, so less stable and not yet feature complete.
:  32 bit sqlite3*.obj and 64 bit SQLite3 dll
In order to maintain the source code repository in a decent size, we excluded the {\f1\fs20 sqlite3*.obj} storage in it, but provide the full source code of the {\i @*SQlite3@} engine in the corresponding {\f1\fs20 sqlite3.c} file, ready to be compiled with all conditional defined as expected by {\f1\fs20 SynSQlite3Static.pas}.
Therefore, {\f1\fs20 sqlite3.obj} and {\f1\fs20 sqlite3fts.obj} files are available as a separated download, from @http://synopse.info/files/sqlite3obj.7z
................................................................................
|{\f1\fs20 SynBz.pas bunzipasm.inc}|fast BZ2 compression/decompression
|{\f1\fs20 SynBzPas.pas}|pascal implementation of BZ2 decompression
|{\f1\fs20 SynCommons.pas}|common functions used by most Synopse projects
|{\f1\fs20 SynCrtSock.pas}|classes implementing @*HTTP@/1.1 client and server protocol
|{\f1\fs20 SynCrypto.pas}|fast cryptographic routines (hashing and cypher)
|{\f1\fs20 SynDprUses.inc}|generic header included in the beginning of the uses clause of a .dpr source code
|{\f1\fs20 SynGdiPlus.pas}|GDI+ library API access with anti-aliasing drawing
|{\f1\fs20 SynLog.pas}|logging functions used by most Synopse projects
|{\f1\fs20 SynLZ.pas}|@**SynLZ@ compression decompression unit - used by {\f1\fs20 SynCommons.pas}
|{\f1\fs20 SynLZO.pas}|LZO compression decompression unit
|{\f1\fs20 SynMemoEx.pas}|Synopse extended {\f1\fs20 TMemo} visual component (used e.g. in {\i @*SynProject@}) - for pre-Unicode {\i Delphi} only
|{\f1\fs20 SynMongoDB.pas}|Direct {\i @*MongoDB@} @*NoSQL@ database access
|{\f1\fs20 SynMustache.pas}|{\i @*Mustache@} logic-less template engine
|{\f1\fs20 SynPdf.pas}|@*PDF@ file generation unit
|{\f1\fs20 SynScaleMM.pas}|multi-thread friendly memory manager unit - not finished yet
................................................................................
|{\f1\fs20 SynSelfTests.pas}|automated @*test@s for {\i mORMot} Framework
|{\f1\fs20 SynSMAPI.pas}|{\i SpiderMonkey} JavaScript engine API definition
|{\f1\fs20 SynSM.pas}|{\i SpiderMonkey} JavaScript engine higher level classes
|{\f1\fs20 SynSQLite3.pas}|{\i @*SQLite3@} embedded Database engine
|{\f1\fs20 SynSQLite3Static.pas}|statically linked @*SQLite3@ engine (for Win32)
|{\f1\fs20 SynSSPIAuth.pas}|low level access to Windows Authentication
|{\f1\fs20 SynTaskDialog.*}|implement TaskDialog window\line (native on Vista/Seven, emulated on XP)
|{\f1\fs20 SynTest.pas}|cross-compiler unitary tests functions
|{\f1\fs20 SynWinSock.pas}|low level access to network Sockets for the Windows platform
|{\f1\fs20 SynZip.pas deflate.obj trees.obj}|low-level access to ZLib compression, 1.2.5
|{\f1\fs20 SynZipFiles.pas}|high-level access to .zip archive file compression
|{\f1\fs20 Synopse.inc}|generic header to be included in all units to set some global conditional definitions
|{\f1\fs20 vista.*}|A resource file enabling theming under XP
|{\f1\fs20 vistaAdm.*}|A resource file enabling theming under XP and Administrator rights under Vista
|%
................................................................................
|\b File|Description\b0
|{\f1\fs20 @*SynDB@.pas}|abstract database direct access classes
|{\f1\fs20 SynOleDB.pas}|fast @*OleDB@ direct access classes
|{\f1\fs20 SynDBODBC.pas}|fast @*ODBC@ direct access classes
|{\f1\fs20 SynDBOracle.pas}|{\i @*Oracle@} DB direct access classes (via OCI)
|{\f1\fs20 SynDBSQLite3.pas}|{\i @*SQLite3@} direct access classes
|{\f1\fs20 SynDBDataset.pas}|{\f1\fs20 @*TDataSet@} / {\f1\fs20 @*TQuery@}-like access classes\line (drivers included in {\f1\fs20 SynDBDataset} sub-folder)
|{\f1\fs20 SynDBRemote.pas}|remote access over HTTP
|{\f1\fs20 SynDBVCL.pas}|DB VCL read-only dataset using {\f1\fs20 SynDB.pas} data access
|{\f1\fs20 SynDBZEOS.pas}|{\i @*Zeos@Lib} / ZDBC direct access classes
|%
:   SynDBDataset folder
In a {\f1\fs20 SynDBDataset} folder, some external database providers are available, to be used with the {\f1\fs20 SynDBDataset.pas} classes:
|%30%70
|\b File|Description\b0
................................................................................
: Upgrading from a 1.17 revision
If you are upgrading from an older revision of the framework, your own source code should be updated.
For instance, some units where renamed, and some breaking changes introduced by enhanced features. As a consequence, a direct update is not possible.
To properly upgrade to the latest revision:
1. Erase or rename your whole previous {\f1\fs20 #\\Lib} directory.
2. Download latest 1.18 revision files as stated just above.
3. Change your references to {\i mORMot} units:
- Add in your uses clause {\f1\fs20 SynTests.pas} if you use testing features;
- Add in your uses clause {\f1\fs20 SynLog.pas} if you use logging features;
- Rename in your uses clauses any {\f1\fs20 SQLite3Commons} reference into {\f1\fs20 mORMot.pas};
- Rename in your uses clauses any {\f1\fs20 SQLite3} reference into {\f1\fs20 mORMotSQLite3.pas};
- Rename in your uses clauses any other {\f1\fs20 SQlite3*} reference into {\f1\fs20 mORMot*};
- Add in one of your uses clause a reference to the {\f1\fs20 SynSQLite3Static.pas} unit (for {\i Win32} or {\i Linux}).
4. Consult the units' headers about 1.18 for breaking changes, mainly:
- Introducing {\f1\fs20 @*TID@ = type Int64} as {\f1\fs20 TSQLRecord.ID} @*primary key@, {\f1\fs20 TIDDynArray} as an array, and @*TRecordReference@ now declared as {\f1\fs20 Int64} instead of plain {\f1\fs20 PtrInt} / {\f1\fs20 integer};
- Renamed {\f1\fs20 Iso8601} low-level structure as {\f1\fs20 @*TTimeLogBits@};

Changes to SQLite3/Samples/07 - SynTest/SynTestTest.pas.

1
2
3
4
5
6

7
8
9
10
11
12
13
unit SynTestTest;

interface

uses
  SynCommons;


function Adding(A,B: double): Double; overload;
function Adding(A,B: integer): integer; overload;

function Multiply(A,B: double): Double; overload;
function Multiply(A,B: integer): integer; overload;






|
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
unit SynTestTest;

interface

uses
  SynCommons,
  SynTests;

function Adding(A,B: double): Double; overload;
function Adding(A,B: integer): integer; overload;

function Multiply(A,B: double): Double; overload;
function Multiply(A,B: integer): integer; overload;

Changes to SQLite3/Samples/10 - Background Http service/httpservice.dpr.

4
5
6
7
8
9
10

11
12
13
14
15
16
17
uses
  {$I SynDprUses.inc}
  Windows,
  Classes,
  SysUtils,
  WinSvc,
  SynCommons,

  mORMotService,
  mORMot,
  mORMotSQLite3, SynSQLite3Static,
  mORMotHTTPServer,
  SampleData in '..\01 - In Memory ORM\SampleData.pas';









>







4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
uses
  {$I SynDprUses.inc}
  Windows,
  Classes,
  SysUtils,
  WinSvc,
  SynCommons,
  SynLog,
  mORMotService,
  mORMot,
  mORMotSQLite3, SynSQLite3Static,
  mORMotHTTPServer,
  SampleData in '..\01 - In Memory ORM\SampleData.pas';


Changes to SQLite3/Samples/10 - Background Http service/httpserviceSetup.dpr.

4
5
6
7
8
9
10

11
12
13
14
15
16
17
uses
  {$I SynDprUses.inc}
  Windows,
  Classes,
  SysUtils,
  WinSvc,
  SynCommons,

  mORMot,
  mORMotService;

/// if we will run the service with administrator rights
// - otherwise, ensure you registered the URI /root:8080
{$R VistaAdm.res}








>







4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
uses
  {$I SynDprUses.inc}
  Windows,
  Classes,
  SysUtils,
  WinSvc,
  SynCommons,
  SynLog,
  mORMot,
  mORMotService;

/// if we will run the service with administrator rights
// - otherwise, ensure you registered the URI /root:8080
{$R VistaAdm.res}

Changes to SQLite3/Samples/11 - Exception logging/LogViewMain.pas.

6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ImgList, StdCtrls, CheckLst, Menus, ExtCtrls, ShellAPI, Grids, Clipbrd, 
{$WARN UNIT_PLATFORM OFF}
  FileCtrl,
{$WARN UNIT_PLATFORM ON}
  SynCommons, mORMotHttpServer;

type
  TMainLogView = class(TForm)
    PanelLeft: TPanel;
    BtnBrowse: TButton;
    EventsList: TCheckListBox;
    FilterMenu: TPopupMenu;







|







6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ImgList, StdCtrls, CheckLst, Menus, ExtCtrls, ShellAPI, Grids, Clipbrd, 
{$WARN UNIT_PLATFORM OFF}
  FileCtrl,
{$WARN UNIT_PLATFORM ON}
  SynCommons, SynLog, mORMotHttpServer;

type
  TMainLogView = class(TForm)
    PanelLeft: TPanel;
    BtnBrowse: TButton;
    EventsList: TCheckListBox;
    FilterMenu: TPopupMenu;

Changes to SQLite3/Samples/11 - Exception logging/LoggingTest.dpr.

18
19
20
21
22
23
24
25

26
27
28
29
30
31
32
  SysUtils,
  ComObj,
  //SynZip,
{$ifdef CONDITIONALEXPRESSIONS}
  mORMot,
  SynSelfTests,
{$endif}
  SynCommons;


type
  /// a class just to show how methods are handled
  TTestLogClass = class
  protected
    procedure TestLog;
  end;







|
>







18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
  SysUtils,
  ComObj,
  //SynZip,
{$ifdef CONDITIONALEXPRESSIONS}
  mORMot,
  SynSelfTests,
{$endif}
  SynCommons,
  SynLog;

type
  /// a class just to show how methods are handled
  TTestLogClass = class
  protected
    procedure TestLog;
  end;

Changes to SQLite3/Samples/11 - Exception logging/Map2Mab.dpr.

7
8
9
10
11
12
13
14

15
16
17
18
19
20
21
// - if no file name is specified, will process '*.map' into '*.mab'
// - you can make map2mapb.exe file small if you define LVCL as conditional in
// the Project options and set the ..\lib\LVCL directories as expected
program Map2Mab;

uses
  SysUtils,
  SynCommons;


procedure Process(const FileName: TFileName);
var SR: TSearchRec;
    Path, FN: TFileName;
    Ext: integer;
begin
  Ext := GetFileNameExtIndex(FileName,'map,exe,dll,ocx,bpl');







|
>







7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// - if no file name is specified, will process '*.map' into '*.mab'
// - you can make map2mapb.exe file small if you define LVCL as conditional in
// the Project options and set the ..\lib\LVCL directories as expected
program Map2Mab;

uses
  SysUtils,
  SynCommons,
  SynLog;

procedure Process(const FileName: TFileName);
var SR: TSearchRec;
    Path, FN: TFileName;
    Ext: integer;
begin
  Ext := GetFileNameExtIndex(FileName,'map,exe,dll,ocx,bpl');

Changes to SQLite3/Samples/11 - Exception logging/MyLibrary.dpr.

4
5
6
7
8
9
10
11

12
13
14
15
16
17
18
19
20
21
22
23
{
  In the Project / Options / Linker tab, the Map files option should be set
  to detailed, in order to demonstrate how libraries can have their own
  symbols file (we need a .map to have this information and create its .mab)

}
uses
  SynCommons;


{$R *.res}

procedure Test;
begin
   TSynLog.Family.Level := LOG_VERBOSE;
   TSynLog.Enter.Log(sllDebug, 'Called from Test exported procedure');
end;

exports Test;

end.







|
>












4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
  In the Project / Options / Linker tab, the Map files option should be set
  to detailed, in order to demonstrate how libraries can have their own
  symbols file (we need a .map to have this information and create its .mab)

}
uses
  SynCommons,
  SynLog;

{$R *.res}

procedure Test;
begin
   TSynLog.Family.Level := LOG_VERBOSE;
   TSynLog.Enter.Log(sllDebug, 'Called from Test exported procedure');
end;

exports Test;

end.

Changes to SQLite3/Samples/11 - Exception logging/RemoteLogMain.pas.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
unit RemoteLogMain;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, SynCommons, mORMot, mORMotHttpClient;

type
  TMainForm = class(TForm)
    grpEvent: TGroupBox;
    cbbEvent: TComboBox;
    edtText: TEdit;
    btnEventSend: TButton;






|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
unit RemoteLogMain;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, SynCommons, SynLog, mORMot, mORMotHttpClient;

type
  TMainForm = class(TForm)
    grpEvent: TGroupBox;
    cbbEvent: TComboBox;
    edtText: TEdit;
    btnEventSend: TButton;

Changes to SQLite3/Samples/11 - Exception logging/UnSynLz.dpr.

9
10
11
12
13
14
15
16

17
18
19
20
21
22
23
// the Project options and set the ..\lib\LVCL directories as expected
program UnSynLz;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  SynCommons;


procedure Process(const FileName: TFileName);
var SR: TSearchRec;
    Path: TFileName;
begin
  if (GetFileNameExtIndex(FileName,'synlz')=0) and
     (FindFirst(FileName,faAnyFile,SR)=0) then







|
>







9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// the Project options and set the ..\lib\LVCL directories as expected
program UnSynLz;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  SynCommons,
  SynLog;

procedure Process(const FileName: TFileName);
var SR: TSearchRec;
    Path: TFileName;
begin
  if (GetFileNameExtIndex(FileName,'synlz')=0) and
     (FindFirst(FileName,faAnyFile,SR)=0) then

Changes to SQLite3/Samples/14 - Interface based services/Project14Server.dpr.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
program Project14Server;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  SynCommons, mORMot,
  mORMotSQLite3, SynSQLite3Static,
  Project14Interface;

type
  TServiceCalculator = class(TInterfacedObject, ICalculator)
  public
    function Add(n1,n2: integer): integer;






|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
program Project14Server;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  SynCommons, SynLog, mORMot,
  mORMotSQLite3, SynSQLite3Static,
  Project14Interface;

type
  TServiceCalculator = class(TInterfacedObject, ICalculator)
  public
    function Add(n1,n2: integer): integer;

Changes to SQLite3/Samples/14 - Interface based services/Project14ServerExternal.dpr.

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

{$APPTYPE CONSOLE}

uses
  SysUtils,
  mORMot,
  mORMotSQLite3,
  SynCommons,
  SynDB,
  SynDBSQLite3, SynSQLite3, SynSQLite3Static,
  mORMotDB,
  Project14Interface;

type
  TServiceCalculator = class(TInterfacedObject, ICalculator)







|







7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

{$APPTYPE CONSOLE}

uses
  SysUtils,
  mORMot,
  mORMotSQLite3,
  SynCommons, SynLog,
  SynDB,
  SynDBSQLite3, SynSQLite3, SynSQLite3Static,
  mORMotDB,
  Project14Interface;

type
  TServiceCalculator = class(TInterfacedObject, ICalculator)

Changes to SQLite3/Samples/14 - Interface based services/Project14ServerHttp.dpr.

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
program Project14ServerHttp;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Classes,
  SynCommons,
  mORMot,
  mORMotHttpServer,
  Project14Interface in 'Project14Interface.pas';

type
  TServiceCalculator = class(TInterfacedObject, ICalculator)
  public







|







2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
program Project14ServerHttp;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Classes,
  SynCommons, SynLog,
  mORMot,
  mORMotHttpServer,
  Project14Interface in 'Project14Interface.pas';

type
  TServiceCalculator = class(TInterfacedObject, ICalculator)
  public

Changes to SQLite3/Samples/14 - Interface based services/Project14ServerHttpWeak.dpr.

3
4
5
6
7
8
9

10
11
12
13
14
15
16

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Classes,
  SynCommons,

  mORMot,
  mORMotSQLite3,
  mORMotHttpServer,
  Project14Interface;

type
  TServiceCalculator = class(TInterfacedObject, ICalculator)







>







3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Classes,
  SynCommons,
  SynLog,
  mORMot,
  mORMotSQLite3,
  mORMotHttpServer,
  Project14Interface;

type
  TServiceCalculator = class(TInterfacedObject, ICalculator)

Changes to SQLite3/Samples/16 - Execute SQL via services/Project16ServerHttp.dpr.

6
7
8
9
10
11
12

13
14
15
16
17
18
19
//   first line of uses clause must be   {$I SynDprUses.inc}
uses
  {$I SynDprUses.inc}
  Windows,
  SysUtils,
  Classes,
  SynCommons,

  mORMot,
  mORMotDB,
  mORMotHttpServer,
  SynDB,
  SynDBOracle,
  SynDBSQLite3, SynSQLite3Static,
  SynOleDB,







>







6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//   first line of uses clause must be   {$I SynDprUses.inc}
uses
  {$I SynDprUses.inc}
  Windows,
  SysUtils,
  Classes,
  SynCommons,
  SynLog,
  mORMot,
  mORMotDB,
  mORMotHttpServer,
  SynDB,
  SynDBOracle,
  SynDBSQLite3, SynSQLite3Static,
  SynOleDB,

Changes to SQLite3/Samples/18 - AJAX ExtJS Grid/Unit2.pas.

1
2
3
4
5
6
7

8
9
10
11
12
13
14
15
unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls,

  SynCommons, mORMot, mORMotSQLite3, SynSQLite3Static, mORMotHttpServer; 

type
  TSQLSampleRecord = class(TSQLRecord)
  private
    fName: RawUTF8;
    fQuestion: RawUTF8;
    fTimeD: TDateTime;







>
|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls,
  SynCommons, SynLog,
  mORMot, mORMotSQLite3, SynSQLite3Static, mORMotHttpServer; 

type
  TSQLSampleRecord = class(TSQLRecord)
  private
    fName: RawUTF8;
    fQuestion: RawUTF8;
    fTimeD: TDateTime;

Changes to SQLite3/Samples/19 - AJAX ExtJS FishFacts/Unit2.pas.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ShellAPI,
  SynCommons, SynZip, SynCrtSock,
  mORMot, mORMotSQLite3, SynSQLite3Static, mORMotHttpServer;

type
  TSQLBiolife = class(TSQLRecord)
  public
    fSpecies_No: integer;
    fCategory: RawUTF8;







|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ShellAPI,
  SynCommons, SynLog, SynZip, SynCrtSock,
  mORMot, mORMotSQLite3, SynSQLite3Static, mORMotHttpServer;

type
  TSQLBiolife = class(TSQLRecord)
  public
    fSpecies_No: integer;
    fCategory: RawUTF8;

Changes to SQLite3/Samples/20 - DTO interface based service/Project20ServerInMemory.dpr.

5
6
7
8
9
10
11

12
13
14
15
16
17
18

//   first line of uses clause must be   {$I SynDprUses.inc}
uses
  {$I SynDprUses.inc}
  SysUtils,
  Classes,
  SynCommons,

  mORMot,
  mORMotHttpServer,
  Project20Interface;

type
  TAirportService = class(TInterfacedObject, IAirportService)
  public







>







5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

//   first line of uses clause must be   {$I SynDprUses.inc}
uses
  {$I SynDprUses.inc}
  SysUtils,
  Classes,
  SynCommons,
  SynLog,
  mORMot,
  mORMotHttpServer,
  Project20Interface;

type
  TAirportService = class(TInterfacedObject, IAirportService)
  public

Changes to SQLite3/Samples/21 - HTTP Client-Server performance/Project21HttpClientMain.pas.

27
28
29
30
31
32
33


34
35
36
37
38
39
40
var
  MainForm: TMainForm;

implementation

uses
  SynCommons,


  mORMot,
  SynSelfTests;

{$R *.dfm}

procedure TMainForm.lbledtClientPerThreadInstanceCountKeyPress(
  Sender: TObject; var Key: Char);







>
>







27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
var
  MainForm: TMainForm;

implementation

uses
  SynCommons,
  SynLog,
  SynTests,
  mORMot,
  SynSelfTests;

{$R *.dfm}

procedure TMainForm.lbledtClientPerThreadInstanceCountKeyPress(
  Sender: TObject; var Key: Char);

Changes to SQLite3/Samples/21 - HTTP Client-Server performance/Project21HttpServer.dpr.

5
6
7
8
9
10
11

12
13
14
15
16
17
18

// first line of uses clause must be   {$I SynDprUses.inc}
uses
  {$I SynDprUses.inc}
  SysUtils,
  Classes,
  SynCommons,

  mORMot,
  mORMotSQlite3,
  SynSQLite3,
  SynSQLite3Static,
  mORMotHttpServer;

type







>







5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

// first line of uses clause must be   {$I SynDprUses.inc}
uses
  {$I SynDprUses.inc}
  SysUtils,
  Classes,
  SynCommons,
  SynLog,
  mORMot,
  mORMotSQlite3,
  SynSQLite3,
  SynSQLite3Static,
  mORMotHttpServer;

type

Changes to SQLite3/Samples/23 - JavaScript Tests/SynSMSelfTest.pas.

54
55
56
57
58
59
60
61


62
63
64
65
66
67
68
{$I Synopse.inc} // define HASINLINE USETYPEINFO CPU32 CPU64
{$I SynSM.inc}   // define SM_DEBUG JS_THREADSAFE CONSIDER_TIME_IN_Z

uses
  SysUtils,
  Math,
  SynCrtSock,
  SynCommons;



/// this is the main entry point of the tests
// - this procedure will create a console, then run all available tests
procedure SynSMConsoleTests;


implementation







|
>
>







54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
{$I Synopse.inc} // define HASINLINE USETYPEINFO CPU32 CPU64
{$I SynSM.inc}   // define SM_DEBUG JS_THREADSAFE CONSIDER_TIME_IN_Z

uses
  SysUtils,
  Math,
  SynCrtSock,
  SynCommons,
  SynLog,
  SynTests;

/// this is the main entry point of the tests
// - this procedure will create a console, then run all available tests
procedure SynSMConsoleTests;


implementation

Changes to SQLite3/Samples/24 - MongoDB/MongoDBTestCases.pas.

7
8
9
10
11
12
13

14
15
16
17
18
19
20
// if defined, will test with 5000 records instead of the default 100 records
{.$define ADD5000}

uses
  SysUtils,
  Variants,
  SynCommons,

  SynMongoDB,
  mORMot,
  SynSQLite3Static,
  mORMotSQLite3,
  mORMotMongoDB;

type







>







7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// if defined, will test with 5000 records instead of the default 100 records
{.$define ADD5000}

uses
  SysUtils,
  Variants,
  SynCommons,
  SynTests,
  SynMongoDB,
  mORMot,
  SynSQLite3Static,
  mORMotSQLite3,
  mORMotMongoDB;

type

Changes to SQLite3/Samples/24 - MongoDB/MongoDBTests.dpr.

4
5
6
7
8
9
10

11
12
13
14
15
16
17
{$APPTYPE CONSOLE}

{$I Synopse.inc} // define HASINLINE USETYPEINFO CPU32 CPU64 OWNNORMTOUPPER

uses
  {$I SynDprUses.inc}
  SynCommons,

  mORMot,
  MongoDBTestCases;

begin
  {$ifdef WITHLOG}
  //SQLite3Log.Family.Level := LOG_VERBOSE;
  TSynLogTestLog := SQLite3Log;







>







4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{$APPTYPE CONSOLE}

{$I Synopse.inc} // define HASINLINE USETYPEINFO CPU32 CPU64 OWNNORMTOUPPER

uses
  {$I SynDprUses.inc}
  SynCommons,
  SynLog,
  mORMot,
  MongoDBTestCases;

begin
  {$ifdef WITHLOG}
  //SQLite3Log.Family.Level := LOG_VERBOSE;
  TSynLogTestLog := SQLite3Log;

Changes to SQLite3/Samples/25 - JSON performance/JSONPerfTestCases.pas.

63
64
65
66
67
68
69

70
71
72
73
74
75
76
  SynCrtSock,
  SynZip,
  {$ifdef TESTBSON}
  SynMongoDB,
  {$endif}
  mORMot,
  SynCommons,

  SynCrossPlatformJSON;


const
  // number of iterations for TTestJSONBenchmarking.SmallContent
  SAMPLE_JSON_1_COUNT = 50000;








>







63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
  SynCrtSock,
  SynZip,
  {$ifdef TESTBSON}
  SynMongoDB,
  {$endif}
  mORMot,
  SynCommons,
  SynTests,
  SynCrossPlatformJSON;


const
  // number of iterations for TTestJSONBenchmarking.SmallContent
  SAMPLE_JSON_1_COUNT = 50000;

Changes to SQLite3/Samples/26 - RESTful ORM/RESTServerClass.pas.

2
3
4
5
6
7
8

9
10
11
12
13
14
15

interface

uses
  SysUtils,
  Classes,
  SynCommons,

  mORMot,
  RESTData;

type
  ENoteServer = class(EORMException);
  
  TNoteServer = class(TSQLRestServerFullMemory)







>







2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

interface

uses
  SysUtils,
  Classes,
  SynCommons,
  SynLog,
  mORMot,
  RESTData;

type
  ENoteServer = class(EORMException);
  
  TNoteServer = class(TSQLRestServerFullMemory)

Changes to SQLite3/Samples/26 - RESTful ORM/RESTserver.dpr.

5
6
7
8
9
10
11

12
13
14
15
16
17
18
uses
  {$I SynDprUses.inc}
  Windows,
  Classes,
  SysUtils,
  WinSvc,
  SynCommons,

  mORMot,
  SynCrtSock,
  mORMotHTTPServer,
  RESTData,
  RESTServerClass in 'RESTServerClass.pas';

var ORMServer: TNoteServer;







>







5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
uses
  {$I SynDprUses.inc}
  Windows,
  Classes,
  SysUtils,
  WinSvc,
  SynCommons,
  SynLog,
  mORMot,
  SynCrtSock,
  mORMotHTTPServer,
  RESTData,
  RESTServerClass in 'RESTServerClass.pas';

var ORMServer: TNoteServer;

Changes to SQLite3/Samples/27 - CrossPlatform Clients/Project14ServerHttpWrapper.dpr.

1
2
3
4
5
6
7
8
9

10
11
12
13
14
15
16
/// this server will demonstrate how to publish code generation wrappers 
program Project14ServerHttpWrapper;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Classes,
  SynCommons,

  mORMot,
  mORMotHttpServer,
  mORMotWrappers,
  Project14Interface in '..\14 - Interface based services\Project14Interface.pas';

type
  TServiceCalculator = class(TInterfacedObject, ICalculator)
|








>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// this server will demonstrate how to publish code generation wrappers
program Project14ServerHttpWrapper;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Classes,
  SynCommons,
  SynLog,
  mORMot,
  mORMotHttpServer,
  mORMotWrappers,
  Project14Interface in '..\14 - Interface based services\Project14Interface.pas';

type
  TServiceCalculator = class(TInterfacedObject, ICalculator)

Changes to SQLite3/Samples/27 - CrossPlatform Clients/RegressionTestsServer.dpr.

4
5
6
7
8
9
10

11
12
13
14
15
16
17

{$APPTYPE CONSOLE}

uses
  {$I SynDprUses.inc}
  PeopleServer,
  SynCommons,

  mORMot,
  SysUtils;

begin
  // define the log level
  if false then
  with TSQLLog.Family do begin







>







4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

{$APPTYPE CONSOLE}

uses
  {$I SynDprUses.inc}
  PeopleServer,
  SynCommons,
  SynLog,
  mORMot,
  SysUtils;

begin
  // define the log level
  if false then
  with TSQLLog.Family do begin

Changes to SQLite3/Samples/28 - Simple RESTful ORM Server/RESTserver.dpr.

3
4
5
6
7
8
9

10
11
12
13
14
15
16

// see http://synopse.info/forum/viewtopic.php?pid=10882#p10882

{$APPTYPE CONSOLE}

uses
  SynCommons,          // framework core

  mORMot,              // RESTful server & ORM
  mORMotSQLite3,       // SQLite3 engine as ORM core
  SynSQLite3Static,    // staticaly linked SQLite3 engine
  mORMotDB,            // ORM using external DB
  mORMotHttpServer,    // HTTP server for RESTful server
  SynDB,               // external DB core
  SynDBODBC,           // external DB access via ODBC







>







3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

// see http://synopse.info/forum/viewtopic.php?pid=10882#p10882

{$APPTYPE CONSOLE}

uses
  SynCommons,          // framework core
  SynLog,              // logging features
  mORMot,              // RESTful server & ORM
  mORMotSQLite3,       // SQLite3 engine as ORM core
  SynSQLite3Static,    // staticaly linked SQLite3 engine
  mORMotDB,            // ORM using external DB
  mORMotHttpServer,    // HTTP server for RESTful server
  SynDB,               // external DB core
  SynDBODBC,           // external DB access via ODBC

Changes to SQLite3/Samples/28 - Simple RESTful ORM Server/RESTserver_wrappers.dpr.

4
5
6
7
8
9
10

11
12
13
14
15
16
17

// see http://synopse.info/forum/viewtopic.php?pid=10882#p10882

{$APPTYPE CONSOLE}

uses
  SynCommons,          // framework core

  mORMot,              // RESTful server & ORM
  mORMotSQLite3,       // SQLite3 engine as ORM core
  SynSQLite3Static,    // staticaly linked SQLite3 engine
  mORMotDB,            // ORM using external DB
  mORMotHttpServer,    // HTTP server for RESTful server
  SynDB,               // external DB core
  SynDBODBC,           // external DB access via ODBC







>







4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

// see http://synopse.info/forum/viewtopic.php?pid=10882#p10882

{$APPTYPE CONSOLE}

uses
  SynCommons,          // framework core
  SynLog,              // logging features
  mORMot,              // RESTful server & ORM
  mORMotSQLite3,       // SQLite3 engine as ORM core
  SynSQLite3Static,    // staticaly linked SQLite3 engine
  mORMotDB,            // ORM using external DB
  mORMotHttpServer,    // HTTP server for RESTful server
  SynDB,               // external DB core
  SynDBODBC,           // external DB access via ODBC

Changes to SQLite3/Samples/30 - MVC Server/MVCViewModel.pas.

4
5
6
7
8
9
10


11
12
13
14
15
16
17
interface

uses
  SysUtils,
  Contnrs,
  Variants,
  SynCommons,


  mORMot,
  mORMotMVC,
  MVCModel;

type
  /// defines the main ViewModel/Controller commands of the BLOG web site
  // - typical URI are:







>
>







4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface

uses
  SysUtils,
  Contnrs,
  Variants,
  SynCommons,
  SynLog,
  SynTests,
  mORMot,
  mORMotMVC,
  MVCModel;

type
  /// defines the main ViewModel/Controller commands of the BLOG web site
  // - typical URI are:

Changes to SQLite3/TestOleDB.dpr.

4
5
6
7
8
9
10

11
12
13
14
15
16
17

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Classes,
  SynCommons,

{$ifndef DELPHI5OROLDER}
  mORMot,
{$endif}
  SynDB,
  SynDBOracle,
  SynOleDB;








>







4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Classes,
  SynCommons,
  SynLog,
{$ifndef DELPHI5OROLDER}
  mORMot,
{$endif}
  SynDB,
  SynDBOracle,
  SynOleDB;

Changes to SQLite3/TestSQL3.dpr.

102
103
104
105
106
107
108


109
110
111
112
113
114
115
  //SynFastWideString,   // no speed benefit for mORMot, but OleDB/Jet works!
  mORMotSelfTests in 'mORMotSelfTests.pas',
  SynLZ in '..\SynLZ.pas',
  SynLZO in '..\SynLZO.pas',
  SynCrypto in '..\SynCrypto.pas',
  SynCrtSock in '..\SynCrtSock.pas',
  SynCommons in '..\SynCommons.pas',


{$ifndef DELPHI5OROLDER}
  {$ifndef LVCL}
  SynMongoDB in '..\SynMongoDB.pas',
  {$ifndef NOVARIANTS}
  SynMustache in '..\SynMustache.pas',
  {$ifndef FPC}
  {$ifndef CPU64}







>
>







102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
  //SynFastWideString,   // no speed benefit for mORMot, but OleDB/Jet works!
  mORMotSelfTests in 'mORMotSelfTests.pas',
  SynLZ in '..\SynLZ.pas',
  SynLZO in '..\SynLZO.pas',
  SynCrypto in '..\SynCrypto.pas',
  SynCrtSock in '..\SynCrtSock.pas',
  SynCommons in '..\SynCommons.pas',
  SynLog in '..\SynLog.pas',
  SynTests in '..\SynTests.pas',
{$ifndef DELPHI5OROLDER}
  {$ifndef LVCL}
  SynMongoDB in '..\SynMongoDB.pas',
  {$ifndef NOVARIANTS}
  SynMustache in '..\SynMustache.pas',
  {$ifndef FPC}
  {$ifndef CPU64}

Changes to SQLite3/TestSQL3.lpi.

Changes to SQLite3/TestSQL3.lps.

1
2
3
4
5
6
7
8
9
10
11
12
13
...
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
































































134

























135
136
137
138
139
140
141
142
143

144
145
146
147

148
149
150
151
152
153
154

155
156
157
158

159
160
161
162

163
164
165
166
167
168
169
170
171




















































































172
173
























174
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
  <ProjectSession>
    <PathDelim Value="\"/>
    <Version Value="9"/>
    <BuildModes Count="4" Active="win32">
      <Item1 Name="void">
        <CompilerOptions>
          <Version Value="11"/>
          <Parsing>
            <SyntaxOptions>
              <UseAnsiStrings Value="False"/>
            </SyntaxOptions>
................................................................................
              <ShowWarn Value="False"/>
              <ShowHints Value="False"/>
            </Verbosity>
          </Other>
        </CompilerOptions>
      </Item4>
    </BuildModes>
    <Units Count="2">
      <Unit0>
        <Filename Value="TestSQL3.dpr"/>
        <IsPartOfProject Value="True"/>
        <IsVisibleTab Value="True"/>
        <TopLine Value="115"/>
        <CursorPos Y="163"/>
        <UsageCount Value="21"/>
        <Loaded Value="True"/>
      </Unit0>
      <Unit1>
        <Filename Value="..\SynSelfTests.pas"/>
        <UnitName Value="SynSelfTests"/>
































































        <EditorIndex Value="1"/>

























        <TopLine Value="2046"/>
        <CursorPos X="12" Y="2068"/>
        <UsageCount Value="10"/>
        <Loaded Value="True"/>
      </Unit1>
    </Units>
    <JumpHistory Count="8" HistoryIndex="7">
      <Position1>
        <Filename Value="TestSQL3.dpr"/>

      </Position1>
      <Position2>
        <Filename Value="TestSQL3.dpr"/>
        <Caret Line="63" Column="24" TopLine="60"/>

      </Position2>
      <Position3>
        <Filename Value="TestSQL3.dpr"/>
        <Caret Line="163" TopLine="115"/>
      </Position3>
      <Position4>
        <Filename Value="..\SynSelfTests.pas"/>

      </Position4>
      <Position5>
        <Filename Value="..\SynSelfTests.pas"/>
        <Caret Line="2056" Column="37" TopLine="2031"/>

      </Position5>
      <Position6>
        <Filename Value="..\SynSelfTests.pas"/>
        <Caret Line="125" Column="4" TopLine="97"/>

      </Position6>
      <Position7>
        <Filename Value="TestSQL3.dpr"/>
        <Caret Line="158" Column="52" TopLine="115"/>
      </Position7>
      <Position8>
        <Filename Value="TestSQL3.dpr"/>
        <Caret Line="163" TopLine="115"/>
      </Position8>




















































































    </JumpHistory>
  </ProjectSession>
























</CONFIG>





|







 







|



<
|
|
|





>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|
|
|
|
|
|
>
|
|
|
<
>
|
|
|
|
|
|
|
>
|
|
|
<
>
|
|
|
<
>
|
|
|
<
|
|
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
...
114
115
116
117
118
119
120
121
122
123
124

125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235

236
237
238
239
240
241
242
243
244
245
246
247

248
249
250
251

252
253
254
255

256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
  <ProjectSession>
    <PathDelim Value="\"/>
    <Version Value="9"/>
    <BuildModes Count="4" Active="linux">
      <Item1 Name="void">
        <CompilerOptions>
          <Version Value="11"/>
          <Parsing>
            <SyntaxOptions>
              <UseAnsiStrings Value="False"/>
            </SyntaxOptions>
................................................................................
              <ShowWarn Value="False"/>
              <ShowHints Value="False"/>
            </Verbosity>
          </Other>
        </CompilerOptions>
      </Item4>
    </BuildModes>
    <Units Count="12">
      <Unit0>
        <Filename Value="TestSQL3.dpr"/>
        <IsPartOfProject Value="True"/>

        <TopLine Value="104"/>
        <CursorPos X="7" Y="108"/>
        <UsageCount Value="471"/>
        <Loaded Value="True"/>
      </Unit0>
      <Unit1>
        <Filename Value="..\SynSelfTests.pas"/>
        <UnitName Value="SynSelfTests"/>
        <EditorIndex Value="11"/>
        <TopLine Value="8423"/>
        <CursorPos Y="8446"/>
        <UsageCount Value="235"/>
        <Loaded Value="True"/>
      </Unit1>
      <Unit2>
        <Filename Value="mORMotMongoDB.pas"/>
        <UnitName Value="mORMotMongoDB"/>
        <EditorIndex Value="10"/>
        <TopLine Value="82"/>
        <CursorPos X="33" Y="117"/>
        <UsageCount Value="235"/>
        <Loaded Value="True"/>
      </Unit2>
      <Unit3>
        <Filename Value="mORMot.pas"/>
        <UnitName Value="mORMot"/>
        <EditorIndex Value="6"/>
        <TopLine Value="26093"/>
        <CursorPos X="38" Y="26116"/>
        <UsageCount Value="235"/>
        <Loaded Value="True"/>
      </Unit3>
      <Unit4>
        <Filename Value="..\SynCrtSock.pas"/>
        <UnitName Value="SynCrtSock"/>
        <EditorIndex Value="7"/>
        <TopLine Value="2143"/>
        <CursorPos X="14" Y="2133"/>
        <UsageCount Value="235"/>
        <Loaded Value="True"/>
      </Unit4>
      <Unit5>
        <Filename Value="..\SynFPCSock.pas"/>
        <UnitName Value="SynFPCSock"/>
        <EditorIndex Value="9"/>
        <TopLine Value="508"/>
        <CursorPos X="28" Y="541"/>
        <UsageCount Value="235"/>
        <Loaded Value="True"/>
      </Unit5>
      <Unit6>
        <Filename Value="mORMotSelfTests.pas"/>
        <UnitName Value="mORMotSelfTests"/>
        <EditorIndex Value="3"/>
        <TopLine Value="34"/>
        <CursorPos X="3" Y="87"/>
        <UsageCount Value="235"/>
        <Loaded Value="True"/>
      </Unit6>
      <Unit7>
        <Filename Value="mORMotHttpClient.pas"/>
        <UnitName Value="mORMotHttpClient"/>
        <EditorIndex Value="8"/>
        <TopLine Value="434"/>
        <CursorPos Y="464"/>
        <UsageCount Value="230"/>
        <Loaded Value="True"/>
      </Unit7>
      <Unit8>
        <Filename Value="..\SynCommons.pas"/>
        <UnitName Value="SynCommons"/>
        <IsVisibleTab Value="True"/>
        <EditorIndex Value="1"/>
        <TopLine Value="35546"/>
        <CursorPos Y="35556"/>
        <UsageCount Value="228"/>
        <Loaded Value="True"/>
      </Unit8>
      <Unit9>
        <Filename Value="..\SynDBRemote.pas"/>
        <UnitName Value="SynDBRemote"/>
        <EditorIndex Value="2"/>
        <UsageCount Value="129"/>
        <Loaded Value="True"/>
      </Unit9>
      <Unit10>
        <Filename Value="..\SynLog.pas"/>
        <UnitName Value="SynLog"/>
        <EditorIndex Value="5"/>
        <TopLine Value="3777"/>
        <CursorPos X="11" Y="3836"/>
        <UsageCount Value="10"/>
        <Loaded Value="True"/>
      </Unit10>
      <Unit11>
        <Filename Value="..\SynTest.pas"/>
        <UnitName Value="SynTest"/>
        <EditorIndex Value="4"/>
        <TopLine Value="703"/>
        <CursorPos X="24" Y="743"/>
        <UsageCount Value="10"/>
        <Loaded Value="True"/>
      </Unit11>
    </Units>
    <JumpHistory Count="30" HistoryIndex="29">
      <Position1>
        <Filename Value="..\SynCommons.pas"/>
        <Caret Line="382" TopLine="331"/>
      </Position1>
      <Position2>
        <Filename Value="..\SynCommons.pas"/>

        <Caret Line="10866" Column="40" TopLine="10837"/>
      </Position2>
      <Position3>
        <Filename Value="..\SynLog.pas"/>
        <Caret Line="3" Column="12"/>
      </Position3>
      <Position4>
        <Filename Value="..\SynLog.pas"/>
        <Caret Line="3834" Column="3" TopLine="3777"/>
      </Position4>
      <Position5>
        <Filename Value="..\SynLog.pas"/>

        <Caret Line="2550" Column="21" TopLine="2520"/>
      </Position5>
      <Position6>
        <Filename Value="..\SynLog.pas"/>

        <Caret Line="3834" Column="30" TopLine="3777"/>
      </Position6>
      <Position7>
        <Filename Value="..\SynLog.pas"/>

      </Position7>
      <Position8>
        <Filename Value="..\SynLog.pas"/>
        <Caret Line="52" Column="38"/>
      </Position8>
      <Position9>
        <Filename Value="..\SynLog.pas"/>
        <Caret Line="1829" Column="45" TopLine="1777"/>
      </Position9>
      <Position10>
        <Filename Value="..\SynLog.pas"/>
        <Caret Line="1901" Column="41" TopLine="1849"/>
      </Position10>
      <Position11>
        <Filename Value="..\SynCommons.pas"/>
        <Caret Line="35564" Column="29" TopLine="35528"/>
      </Position11>
      <Position12>
        <Filename Value="..\SynCommons.pas"/>
      </Position12>
      <Position13>
        <Filename Value="..\SynCommons.pas"/>
        <Caret Line="7122" Column="79" TopLine="7070"/>
      </Position13>
      <Position14>
        <Filename Value="..\SynTest.pas"/>
        <Caret Line="56" TopLine="4"/>
      </Position14>
      <Position15>
        <Filename Value="..\SynTest.pas"/>
        <Caret Line="755" Column="13" TopLine="725"/>
      </Position15>
      <Position16>
        <Filename Value="..\SynTest.pas"/>
        <Caret Line="757" Column="27" TopLine="725"/>
      </Position16>
      <Position17>
        <Filename Value="..\SynTest.pas"/>
        <Caret Line="271" Column="25" TopLine="241"/>
      </Position17>
      <Position18>
        <Filename Value="..\SynTest.pas"/>
        <Caret Line="755" Column="22" TopLine="725"/>
      </Position18>
      <Position19>
        <Filename Value="..\SynTest.pas"/>
        <Caret Line="271" Column="18" TopLine="241"/>
      </Position19>
      <Position20>
        <Filename Value="..\SynTest.pas"/>
        <Caret Line="755" Column="21" TopLine="725"/>
      </Position20>
      <Position21>
        <Filename Value="..\SynTest.pas"/>
      </Position21>
      <Position22>
        <Filename Value="..\SynTest.pas"/>
        <Caret Line="746" Column="39" TopLine="694"/>
      </Position22>
      <Position23>
        <Filename Value="..\SynTest.pas"/>
        <Caret Line="755" Column="21" TopLine="703"/>
      </Position23>
      <Position24>
        <Filename Value="..\SynTest.pas"/>
        <Caret Line="762" Column="14" TopLine="710"/>
      </Position24>
      <Position25>
        <Filename Value="..\SynCommons.pas"/>
        <Caret Line="7142" TopLine="7096"/>
      </Position25>
      <Position26>
        <Filename Value="..\SynTest.pas"/>
        <Caret Line="889" Column="14" TopLine="837"/>
      </Position26>
      <Position27>
        <Filename Value="..\SynTest.pas"/>
        <Caret Line="756" Column="41" TopLine="725"/>
      </Position27>
      <Position28>
        <Filename Value="..\SynTest.pas"/>
      </Position28>
      <Position29>
        <Filename Value="..\SynTest.pas"/>
        <Caret Line="746" Column="67" TopLine="694"/>
      </Position29>
      <Position30>
        <Filename Value="..\SynCommons.pas"/>
      </Position30>
    </JumpHistory>
  </ProjectSession>
  <Debugging>
    <BreakPoints Count="2">
      <Item1>
        <Kind Value="bpkSource"/>
        <WatchScope Value="wpsLocal"/>
        <WatchKind Value="wpkWrite"/>
        <Source Value="..\SynFPCSock.pas"/>
        <Line Value="544"/>
      </Item1>
      <Item2>
        <Kind Value="bpkSource"/>
        <WatchScope Value="wpsLocal"/>
        <WatchKind Value="wpkWrite"/>
        <Source Value="..\SynFPCSock.pas"/>
        <Line Value="534"/>
      </Item2>
    </BreakPoints>
    <Watches Count="1">
      <Item1>
        <Expression Value="result"/>
        <DisplayStyle Value="wdfHex"/>
      </Item1>
    </Watches>
  </Debugging>
</CONFIG>

Changes to SQLite3/TestSQL3Register.dpr.

14
15
16
17
18
19
20

21
22
23
24
25

26
27
28
29
30
31
32
  REGSTR: array[boolean] of string = (
    'Registration', 'Deletion');

{$R VistaAdm.res} // force elevation to Administrator under Vista/Seven

var delete: boolean;
    i: integer;

procedure Call(const Root,Port: RawByteString);
begin
  writeln(REGSTR[delete],' of /',root,':',port,'/+ for http.sys');
  writeln(THttpApiServer.AddUrlAuthorize(root,port,false,'+',delete));
end;

begin
  // perform url registration for http.sys
  // (e.g. to be run as administrator under Windows Vista/Seven)
  delete := (ParamCount=1) and SameText(ParamStr(1),'/DELETE');
  // parameters below must match class function
  //  TTestClientServerAccess.RegisterAddUrl in mORMotHttpServer.pas:
  Call('root','888');







>





>







14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
  REGSTR: array[boolean] of string = (
    'Registration', 'Deletion');

{$R VistaAdm.res} // force elevation to Administrator under Vista/Seven

var delete: boolean;
    i: integer;

procedure Call(const Root,Port: RawByteString);
begin
  writeln(REGSTR[delete],' of /',root,':',port,'/+ for http.sys');
  writeln(THttpApiServer.AddUrlAuthorize(root,port,false,'+',delete));
end;

begin
  // perform url registration for http.sys
  // (e.g. to be run as administrator under Windows Vista/Seven)
  delete := (ParamCount=1) and SameText(ParamStr(1),'/DELETE');
  // parameters below must match class function
  //  TTestClientServerAccess.RegisterAddUrl in mORMotHttpServer.pas:
  Call('root','888');

Changes to SQLite3/mORMot.pas.

1163
1164
1165
1166
1167
1168
1169
1170


1171
1172
1173
1174
1175
1176
1177
    Variants,
  {$endif}
{$endif}
  SysUtils,
{$ifdef SSPIAUTH}
  SynSSPIAuth,
{$endif}
  SynCommons;





{ ************ low level types and constants for handling JSON and fields }

  { Why use JSON? (extracted from the main framework documentation)
    - The JavaScript Object Notation (JSON) is a lightweight computer data







|
>
>







1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
    Variants,
  {$endif}
{$endif}
  SysUtils,
{$ifdef SSPIAUTH}
  SynSSPIAuth,
{$endif}
  SynCommons,
  SynLog,
  SynTests;



{ ************ low level types and constants for handling JSON and fields }

  { Why use JSON? (extracted from the main framework documentation)
    - The JavaScript Object Notation (JSON) is a lightweight computer data

Changes to SQLite3/mORMotDB.pas.

142
143
144
145
146
147
148

149
150
151
152
153
154
155
  Windows,
  {$else}
  SynFPCLinux,
  {$endif}
  SysUtils,
  Classes,
  SynCommons,

  mORMot,
  SynDB;
             
type
  /// REST server with direct access to a SynDB-based external database
  // - handle all REST commands, using the external SQL database connection,
  // and prepared statements







>







142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
  Windows,
  {$else}
  SynFPCLinux,
  {$endif}
  SysUtils,
  Classes,
  SynCommons,
  SynLog,
  mORMot,
  SynDB;
             
type
  /// REST server with direct access to a SynDB-based external database
  // - handle all REST commands, using the external SQL database connection,
  // and prepared statements

Changes to SQLite3/mORMotHttpClient.pas.

140
141
142
143
144
145
146

147
148
149
150
151
152
153
  SysUtils,
  Classes,
  SynCrypto, // for hcSynShaAes
  SynZip,
  SynLZ,
  SynCrtSock,
  SynCommons,

  mORMot;

type
  /// available compression algorithms for transmission
  // - SynLZ is faster then Deflate, but not standard: use hcSynLZ for Delphi
  // clients, but hcDeflate for AJAX or any HTTP clients
  // - with hcSynLZ, the 440 KB JSON for TTestClientServerAccess._TSQLHttpClient







>







140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
  SysUtils,
  Classes,
  SynCrypto, // for hcSynShaAes
  SynZip,
  SynLZ,
  SynCrtSock,
  SynCommons,
  SynLog,
  mORMot;

type
  /// available compression algorithms for transmission
  // - SynLZ is faster then Deflate, but not standard: use hcSynLZ for Delphi
  // clients, but hcDeflate for AJAX or any HTTP clients
  // - with hcSynLZ, the 440 KB JSON for TTestClientServerAccess._TSQLHttpClient

Changes to SQLite3/mORMotHttpServer.pas.

202
203
204
205
206
207
208

209
210
211
212
213
214
215
{$endif}
{$ifdef COMPRESSSYNLZ}
  SynLZ,
{$endif}
  SynCrtSock,
  SynCrypto, // for CompressShaAes()
  SynCommons,

  mORMot;

type
  /// available running options for TSQLHttpServer.Create() constructor
  // - useHttpApi to use kernel-mode HTTP.SYS server (THttpApiServer) with an
  // already registered URI (default way, similar to IIS/WCF security policy
  // as specified by Microsoft) - you would need to register the URI by hand,







>







202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
{$endif}
{$ifdef COMPRESSSYNLZ}
  SynLZ,
{$endif}
  SynCrtSock,
  SynCrypto, // for CompressShaAes()
  SynCommons,
  SynLog,
  mORMot;

type
  /// available running options for TSQLHttpServer.Create() constructor
  // - useHttpApi to use kernel-mode HTTP.SYS server (THttpApiServer) with an
  // already registered URI (default way, similar to IIS/WCF security policy
  // as specified by Microsoft) - you would need to register the URI by hand,

Changes to SQLite3/mORMotMVC.pas.

64
65
66
67
68
69
70

71
72
73
74
75
76
77
  SysUtils,
  Classes,
  {$ifndef LVCL}
  Contnrs,
  {$endif}
  Variants,
  SynCommons,

  SynCrypto,
  SynMustache,
  mORMot;

type

  { ====== Views ====== }







>







64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
  SysUtils,
  Classes,
  {$ifndef LVCL}
  Contnrs,
  {$endif}
  Variants,
  SynCommons,
  SynLog,
  SynCrypto,
  SynMustache,
  mORMot;

type

  { ====== Views ====== }

Changes to SQLite3/mORMotSQLite3.pas.

259
260
261
262
263
264
265

266
267
268
269
270
271
272
273
274
275
  SysUtils,
  Classes,
{$ifndef LVCL}
  Contnrs,
{$endif}
  SynZip,
  SynCommons,

  SynSQLite3,
  mORMot;

{$define INCLUDE_FTS3}
{ define this if you want to include the FTS3/FTS4 feature into the library
  - FTS3 is an SQLite module implementing full-text search
  - will include also FTS4 extension module since 3.7.4
  - see http://www.sqlite.org/fts3.html for documentation
  - is defined by default, but can be unset to save about 50 KB of code size
  - should be defined for SynSQLite3, SynSQLite3Static and mORMotSQLite3 units }







>


|







259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
  SysUtils,
  Classes,
{$ifndef LVCL}
  Contnrs,
{$endif}
  SynZip,
  SynCommons,
  SynLog,
  SynSQLite3,
  mORMot;
  
{$define INCLUDE_FTS3}
{ define this if you want to include the FTS3/FTS4 feature into the library
  - FTS3 is an SQLite module implementing full-text search
  - will include also FTS4 extension module since 3.7.4
  - see http://www.sqlite.org/fts3.html for documentation
  - is defined by default, but can be unset to save about 50 KB of code size
  - should be defined for SynSQLite3, SynSQLite3Static and mORMotSQLite3 units }

Changes to SQLite3/mORMotSelfTests.pas.

79
80
81
82
83
84
85


86
87
88
89
90
91
92
  {$endif}
  Classes,
  SynLZ,
  SynLZO,
  SynCrypto,
  SynCrtSock,
  SynCommons,


{$ifndef DELPHI5OROLDER}
  {$ifdef MSWINDOWS}
  SynBigTable,
  {$endif}
  SynSQLite3,
  SynSQLite3Static,
  mORMot,







>
>







79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
  {$endif}
  Classes,
  SynLZ,
  SynLZO,
  SynCrypto,
  SynCrtSock,
  SynCommons,
  SynLog,
  SynTests,
{$ifndef DELPHI5OROLDER}
  {$ifdef MSWINDOWS}
  SynBigTable,
  {$endif}
  SynSQLite3,
  SynSQLite3Static,
  mORMot,

Changes to SQLite3/mORMotService.pas.

84
85
86
87
88
89
90
91

92
93
94
95
96
97
98
  // define it if you don't need to include the mORMot.pas unit
  // - could save some code size in the final executable
{$endif}

uses
  Windows, WinSVC, Messages, Classes, SysUtils,
  {$ifndef LVCL}Contnrs,{$endif}
  SynCommons

  {$ifndef NOMORMOTKERNEL},
  mORMot
  {$endif} // translated caption needed only if with full UI
  ;

const
  CM_SERVICE_CONTROL_CODE = WM_USER+1000;







|
>







84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
  // define it if you don't need to include the mORMot.pas unit
  // - could save some code size in the final executable
{$endif}

uses
  Windows, WinSVC, Messages, Classes, SysUtils,
  {$ifndef LVCL}Contnrs,{$endif}
  SynCommons,
  SynLog
  {$ifndef NOMORMOTKERNEL},
  mORMot
  {$endif} // translated caption needed only if with full UI
  ;

const
  CM_SERVICE_CONTROL_CODE = WM_USER+1000;

Changes to SynBigTable.pas.

242
243
244
245
246
247
248
249

250
251
252
253
254
255
256
  {$endif}
  {$endif}
  Classes,
  SysUtils,
  {$ifdef FPC}
  Variants,
  {$endif}
  SynCommons;


type
  TSynBigTable = class;

  /// possible actions for CustomHeader() protected virtual method
  TSynBigTableCustomHeader = (sbtRead, sbtBeforeWrite, sbtWrite, sbtAfterRead);








|
>







242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
  {$endif}
  {$endif}
  Classes,
  SysUtils,
  {$ifdef FPC}
  Variants,
  {$endif}
  SynCommons,
  SynTests;

type
  TSynBigTable = class;

  /// possible actions for CustomHeader() protected virtual method
  TSynBigTableCustomHeader = (sbtRead, sbtBeforeWrite, sbtWrite, sbtAfterRead);

Changes to SynCommons.pas.

330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
...
360
361
362
363
364
365
366

367
368
369
370
371
372
373
...
376
377
378
379
380
381
382



383
384
385
386
387
388
389
...
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
...
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
...
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
...
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
...
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
...
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
...
656
657
658
659
660
661
662



663
664
665
666
667
668
669
670
671
....
2299
2300
2301
2302
2303
2304
2305








2306
2307
2308
2309
2310
2311
2312
....
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
....
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
....
3528
3529
3530
3531
3532
3533
3534

3535
3536
3537
3538
3539
3540
3541
....
3839
3840
3841
3842
3843
3844
3845


3846
3847
3848
3849


3850
3851
3852
3853
3854
3855
3856
....
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
....
5219
5220
5221
5222
5223
5224
5225


5226
5227
5228
5229
5230
5231
5232
....
5653
5654
5655
5656
5657
5658
5659






5660
5661
5662
5663
5664
5665
5666
....
6362
6363
6364
6365
6366
6367
6368







6369
6370
6371
6372
6373
6374
6375
....
6496
6497
6498
6499
6500
6501
6502


6503
6504
6505
6506
6507
6508
6509
....
6793
6794
6795
6796
6797
6798
6799

6800
6801
6802
6803
6804
6805
6806
....
7098
7099
7100
7101
7102
7103
7104





























































7105
7106
7107
7108
7109
7110
7111
....
8331
8332
8333
8334
8335
8336
8337
8338
8339
8340
8341
8342
8343
8344
8345
8346
8347
8348
8349
8350
8351
8352
8353
8354
8355
8356
8357
8358
8359
8360
8361
8362
8363
8364
8365
8366
8367
.....
10343
10344
10345
10346
10347
10348
10349
10350
10351
10352
10353
10354
10355
10356

10357





























10358
10359
10360
10361
10362
10363
10364
10365
.....
10446
10447
10448
10449
10450
10451
10452

10453
10454
10455
10456
10457
10458
10459
10460
10461
10462
10463
.....
10713
10714
10715
10716
10717
10718
10719
10720
10721
10722
10723
10724
10725
10726
10727
10728
10729
10730
10731
10732
10733
10734
10735
10736
10737
10738
10739
10740
10741
10742
10743
10744
10745
10746
10747
10748
10749
10750
10751
10752
10753
10754
10755
10756
10757
10758
10759
10760
10761
10762
10763
10764
10765
10766
10767
10768
10769
10770
10771
10772
10773
10774
10775
10776
10777
10778
10779
10780
10781
10782
10783
10784
10785
10786
10787
10788
10789
10790
10791
10792
10793
10794
10795
10796
10797
10798
10799
10800
10801
10802
10803
10804
10805
10806
10807
10808
10809
10810
10811
10812
10813
10814
10815
10816
10817
10818
10819
10820
10821
10822
10823
10824
10825
10826
10827
10828
10829
10830
10831
10832
10833
10834
10835
10836
10837
10838
10839
10840
10841
10842
10843
10844
10845
10846
10847
10848
10849
10850
10851
10852
10853
10854
10855
10856
10857
10858
10859
10860
10861
10862
10863
10864
10865
10866
10867
10868
10869
10870
10871
10872
10873
10874
10875
10876
10877
10878
10879
10880
10881
10882
10883
10884
10885
10886
10887
10888
10889
10890
10891
10892
10893
10894
10895
10896
10897
10898
10899
10900
10901
10902
10903
10904
10905
10906
10907
10908
10909
10910
10911
10912
10913
10914
10915
10916
10917
10918
10919
10920
10921
10922
10923
10924
10925
10926
10927
10928
10929
10930
10931
10932
10933
10934
10935
10936
10937
10938
10939
10940
10941
10942
10943
10944
10945
10946
10947
10948
10949
10950
10951
10952
10953
10954
10955
10956
10957
10958
10959
10960
10961
10962
10963
10964
10965
10966
10967
10968
10969
10970
10971
10972
10973
10974
10975
10976
10977
10978
10979
10980
10981
10982
10983
10984
10985
10986
10987
10988
10989
10990
10991
10992
10993
10994
10995
10996
10997
10998
10999
11000
11001
11002
11003
11004
11005
11006
11007
.....
11015
11016
11017
11018
11019
11020
11021
11022
11023
11024
11025
11026
11027
11028
11029
11030
11031
11032
11033
11034
11035
11036
11037
11038
11039
11040
11041
11042
11043
11044
11045
11046
11047
11048
11049
11050
11051
11052
11053
11054
11055
11056
11057
11058
11059
11060
11061
11062
11063
11064
11065
11066
11067
11068
11069
11070
11071
11072
11073
11074
11075
11076
11077
11078
11079
11080
11081
11082
11083
11084
11085
11086
11087
11088
11089
11090
11091
11092
11093
11094
11095
11096
11097
11098
11099
11100
11101
11102
11103
11104
11105
11106
11107
11108
11109
11110
11111
11112
11113
11114
11115
11116
11117
11118
11119
11120
11121
11122
11123
11124
11125
11126
11127
11128
11129
11130
11131
11132
11133
11134
11135
11136
11137
11138
11139
11140
11141
11142
11143
11144
11145
11146
11147
11148
11149
11150
11151
11152
11153
11154
11155
11156
11157
11158
11159
11160
11161
11162
11163
11164
11165
11166
11167
11168
11169
11170
11171
11172
11173
11174
11175
11176
11177
11178
11179
11180
11181
11182
11183
11184
11185
11186
11187
11188
11189
11190
11191
11192
11193
11194
11195
11196
11197
11198
11199
11200
11201
11202
11203
11204
11205
11206
11207
11208
11209
11210
11211
11212
11213
11214
11215
11216
11217
11218
11219
11220
11221
11222
11223
11224
11225
11226
11227
11228
11229
11230
11231
11232
11233
11234
11235
11236
11237
11238
11239
11240
11241
11242
11243
11244
11245
11246
11247
11248
11249
11250
11251
11252
11253
11254
11255
11256
11257
11258
11259
11260
11261
11262
11263
11264
11265
11266
11267
11268
11269
11270
11271
11272
11273
11274
11275
11276
11277
11278
11279
11280
11281
11282
11283
11284
11285
11286
11287
11288
11289
11290
11291
11292
11293
11294
11295
11296
11297
11298
11299
11300
11301
11302
11303
11304
11305
11306
11307
11308
11309
11310
11311
11312
11313
11314
11315
11316
11317
11318
11319
11320
11321
11322
11323
11324
11325
11326
11327
11328
11329
11330
11331
11332
11333
11334
11335
11336
11337
11338
11339
11340
11341
11342
11343
11344
11345
11346
11347
11348
11349
11350
11351
11352
11353
11354
11355
11356
11357
11358
11359
11360
11361
11362
11363
11364
11365
11366
11367
11368
11369
11370
11371
11372
11373
11374
11375
11376
11377
11378
11379
11380
11381
11382
11383
11384
11385
11386
11387
11388
11389
11390
11391
11392
11393
11394
11395
11396
11397
11398
11399
11400
11401
11402
11403
11404
11405
11406
11407
11408
11409
11410
11411
11412
11413
11414
11415
11416
11417
11418
11419
11420
11421
11422
11423
11424
11425
11426
11427
11428
11429
11430
11431
11432
11433
11434
11435
11436
11437
11438
11439
11440
11441
11442
11443
11444
11445
11446
11447
11448
11449
11450
11451
11452
11453
11454
11455
11456
11457
11458
11459
11460
11461
11462
11463
11464
11465
11466
11467
11468
11469
11470
11471
11472
11473
11474
11475
11476
11477
11478
11479
11480
11481
11482
11483
11484
11485
11486
11487
11488
11489
11490
11491
11492
11493
11494
11495
11496
11497
11498
11499
11500
11501
11502
11503
11504
11505
11506
11507
11508
11509
11510
11511
11512
11513
11514
11515
11516
11517
11518
11519
11520
11521
11522
11523
11524
11525
11526
11527
11528
11529
11530
11531
11532
11533
11534
11535
11536
11537
11538
11539
11540
11541
11542
11543
11544
11545
11546
11547
11548
11549
11550
11551
11552
11553
11554
11555
11556
11557
11558
11559
11560
11561
11562
11563
11564
11565
11566
11567
11568
11569
11570
11571
11572
11573
11574
11575
11576
11577
11578
11579
11580
11581
11582
11583
11584
11585
11586
11587
11588
11589
11590
11591
11592
11593
11594
11595
11596
11597
11598
11599
11600
11601
11602
11603
11604
11605
11606
11607
11608
11609
11610
11611
11612
11613
11614
11615
11616
11617
11618
11619
11620
11621
11622
11623
11624
11625
11626
11627
11628
11629
11630
11631
11632
11633
11634
11635
11636
11637
11638
11639
11640
11641
11642
11643
11644
11645
11646
11647
11648
11649
11650
11651
11652
11653
11654
11655
11656
11657
11658
11659
11660
11661
11662
11663
11664
11665
11666
11667
11668
11669
11670
11671
11672
11673
11674
11675
11676
11677
11678
11679
11680
11681
11682
11683
11684
11685
11686
11687
11688
11689
11690
11691
11692
11693
11694
11695
11696
11697
11698
11699
11700
11701
11702
11703
11704
11705
11706
11707
11708
11709
11710
11711
11712
11713
11714
11715
11716
11717
11718
11719
11720
11721
11722
11723
11724
11725
11726
11727
11728
11729
11730
11731
11732
11733
11734
11735
11736
11737
11738
11739
11740
11741
11742
11743
11744
11745
11746
11747
11748
11749
11750
11751
11752
11753
11754
11755
11756
11757
11758
11759
11760
11761
11762
11763
11764
11765
11766
11767
11768
11769
11770
11771
11772
11773
11774
11775
11776
11777
11778
11779
11780
11781
11782
11783
11784
11785
11786
11787
11788
11789
11790
11791
11792
11793
11794
11795
11796
11797
11798
11799
11800
11801
11802
11803
11804
11805
11806
11807
11808
11809
11810
11811
11812
11813
11814
11815
11816
11817
11818
11819
11820
11821
11822
11823
11824
11825
11826
11827
11828
11829
11830
11831
11832
11833
11834
11835
11836
11837
11838
11839
11840
11841
11842
11843
11844
11845
11846
11847
11848
11849
11850
11851
11852
11853
11854
11855
11856
11857
11858
11859
11860
11861
11862
11863
11864
11865
11866
11867
11868
11869
11870
11871
11872
11873
11874
11875
11876
11877
11878
11879
11880
11881
11882
11883
11884
11885
11886
11887
11888
11889
11890
11891
11892
11893
11894
11895
11896
11897
11898
11899
11900
11901
11902
11903
11904
11905
11906
11907
11908
11909
11910
11911
11912
11913
11914
11915
11916
11917
11918
11919
11920
11921
11922
11923
11924
11925
11926
11927
11928
11929
11930
11931
11932
11933
11934
11935
11936
11937
11938
11939
11940
11941
11942
11943
11944
11945
11946
11947
11948
11949
11950
11951
11952
11953
11954
11955
11956
11957
11958
11959
11960
11961
11962
11963
11964
11965
11966
11967
11968
11969
11970
11971
11972
11973
11974
11975
11976
11977
11978
11979
11980
11981
11982
11983
11984
11985
11986
11987
11988
11989
11990
11991
11992
11993
11994
11995
11996
11997
11998
11999
12000
12001
12002
12003
12004
12005
12006
12007
12008
12009
12010
12011
12012
12013
12014
12015
12016
12017
12018
12019
12020
12021
12022
12023
12024
12025
12026
12027
12028
12029
12030
12031
12032
12033
.....
12035
12036
12037
12038
12039
12040
12041




12042
12043
12044
12045
12046
12047
12048
.....
12067
12068
12069
12070
12071
12072
12073
12074
12075
12076
12077
12078
12079
12080
12081
12082
12083
12084
12085
12086
12087
12088
12089
12090
12091
.....
12100
12101
12102
12103
12104
12105
12106
12107
12108
12109
12110
12111
12112
12113
12114
.....
12193
12194
12195
12196
12197
12198
12199
12200
12201
12202
12203
12204
12205
12206
12207
12208
.....
16991
16992
16993
16994
16995
16996
16997
16998
16999
17000
17001
17002
17003
17004
17005
17006
17007
.....
17009
17010
17011
17012
17013
17014
17015
17016
17017
17018
17019
17020
17021
17022
17023
17024
17025
.....
17033
17034
17035
17036
17037
17038
17039
17040
17041
17042
17043
17044
17045
17046
17047
17048
17049
17050
17051
17052
17053
17054
17055
17056
17057
17058
17059
17060
17061
17062
17063
17064
17065
17066
17067
17068
17069
.....
18696
18697
18698
18699
18700
18701
18702
18703
18704
18705
18706
18707
18708
18709
18710
18711
18712
.....
24742
24743
24744
24745
24746
24747
24748
24749
24750
24751
24752
24753
24754
24755
24756
24757
24758
24759
24760
24761
24762
24763
24764
24765
24766
.....
24785
24786
24787
24788
24789
24790
24791
24792
24793
24794
24795
24796
24797
24798
24799
24800
24801
24802
24803
24804
24805
24806
24807
24808
24809
24810
24811
24812



24813
24814
24815
24816
24817
24818
24819
24820
24821

24822
24823
24824
24825
24826
24827
24828
24829
24830
24831

24832
24833
24834
24835
24836
24837


24838
24839
24840
24841
24842
24843
24844
24845
24846
24847
24848
24849
24850
24851
24852
24853
24854
24855
24856
24857
24858
24859
24860
24861
24862
24863
24864
24865
24866
24867
24868
24869
24870
24871
24872
24873
24874
24875
24876
24877
24878
24879
24880
24881
24882
24883
.....
24927
24928
24929
24930
24931
24932
24933
24934
24935
24936
24937
24938
24939
24940
24941
24942
24943
24944
24945
24946
24947
24948
24949
24950
24951
24952
24953
24954
24955
.....
31001
31002
31003
31004
31005
31006
31007
31008
31009
31010
31011
31012
31013
31014
31015
31016
31017
31018
31019
31020
31021
31022
31023
31024
31025
31026
31027
31028
31029
31030
31031
31032
31033
31034
31035
31036
31037
31038
31039
31040
31041
31042
31043
31044
31045
31046
31047
31048
31049
31050
31051
31052
31053
31054
31055
31056
31057
31058
31059
31060
31061
31062
31063
.....
31064
31065
31066
31067
31068
31069
31070
31071
31072
31073
31074
31075
31076
31077
31078
31079
31080
31081
31082
31083
31084
31085
31086
31087
31088
31089
31090
31091
31092
31093
31094
31095
31096
31097
31098
31099
31100
31101
.....
31114
31115
31116
31117
31118
31119
31120
31121
31122
31123
31124
31125
31126
31127
31128
.....
31167
31168
31169
31170
31171
31172
31173
31174
31175
31176
31177
31178
31179
31180
31181
.....
31195
31196
31197
31198
31199
31200
31201
31202
31203
31204
31205
31206
31207
31208
31209
.....
31215
31216
31217
31218
31219
31220
31221
31222
31223
31224
31225
31226
31227
31228
31229
.....
31283
31284
31285
31286
31287
31288
31289
31290
31291
31292
31293
31294
31295
31296
31297
31298
31299
31300
31301
31302
31303
31304
31305
31306
31307
.....
31409
31410
31411
31412
31413
31414
31415
31416





31417
31418
31419
31420
31421
31422
31423
.....
31521
31522
31523
31524
31525
31526
31527
31528
31529
31530
31531
31532
31533
31534
31535
.....
31557
31558
31559
31560
31561
31562
31563
31564
31565
31566
31567
31568
31569
31570
31571
31572
31573
31574
.....
31577
31578
31579
31580
31581
31582
31583
31584
31585
31586
31587
31588
31589
31590
31591
31592
31593
31594
31595
31596
31597
31598
31599
31600
31601
31602
31603
31604
31605
.....
31679
31680
31681
31682
31683
31684
31685
31686
31687
31688
31689
31690
31691
31692
31693
.....
31699
31700
31701
31702
31703
31704
31705
31706
31707
31708
31709
31710
31711
31712
31713
.....
31777
31778
31779
31780
31781
31782
31783
31784
31785
31786
31787
31788
31789
31790
31791
.....
31815
31816
31817
31818
31819
31820
31821
31822
31823
31824
31825
31826
31827
31828
31829
.....
31831
31832
31833
31834
31835
31836
31837
31838
31839
31840
31841
31842
31843
31844
31845
.....
31849
31850
31851
31852
31853
31854
31855
31856
31857
31858
31859
31860
31861
31862
31863
.....
31976
31977
31978
31979
31980
31981
31982
31983
31984
31985
31986
31987
31988
31989
31990
31991
31992
31993
31994
31995
31996
31997
31998
31999
32000
32001
32002
32003
32004
32005
32006
32007
32008
32009
32010
32011
.....
32048
32049
32050
32051
32052
32053
32054
32055
32056
32057
32058
32059
32060
32061
32062
32063
.....
32099
32100
32101
32102
32103
32104
32105
32106
32107
32108
32109
32110
32111
32112
32113
32114
32115
32116
32117
32118
.....
32154
32155
32156
32157
32158
32159
32160
32161
32162
32163
32164
32165
32166
32167
32168
.....
32199
32200
32201
32202
32203
32204
32205
32206
32207
32208
32209
32210
32211
32212
32213
32214
32215
32216
32217
32218
.....
32241
32242
32243
32244
32245
32246
32247
32248
32249
32250
32251
32252
32253
32254
32255
32256
32257
32258
32259
32260
32261
.....
32272
32273
32274
32275
32276
32277
32278
32279
32280
32281
32282
32283
32284
32285
32286
32287
32288
.....
32289
32290
32291
32292
32293
32294
32295
32296
32297
32298
32299
32300
32301
32302
32303
32304
32305
32306
32307
32308
32309
32310
32311
32312
32313
32314
32315
32316
32317
32318
32319
32320
32321
32322
32323
.....
32331
32332
32333
32334
32335
32336
32337
32338
32339
32340
32341
32342
32343
32344
32345
32346
32347
32348
32349
32350
32351
32352
.....
32357
32358
32359
32360
32361
32362
32363
32364
32365
32366
32367
32368
32369
32370
32371
32372
32373
32374
32375
32376
32377
.....
32379
32380
32381
32382
32383
32384
32385
32386
32387
32388
32389
32390
32391
32392
32393
32394
32395
32396
32397
32398
32399
32400
32401
32402
32403
32404
32405
32406
32407
32408
32409
32410
32411
32412
32413
32414
32415
.....
32416
32417
32418
32419
32420
32421
32422
32423
32424
32425
32426
32427
32428
32429
32430
.....
32598
32599
32600
32601
32602
32603
32604
32605
32606
32607
32608
32609
32610
32611
32612
32613
32614
.....
32712
32713
32714
32715
32716
32717
32718
32719
32720
32721
32722
32723
32724
32725
32726
32727
32728
32729
32730
32731
32732
32733
32734
32735
32736
32737
32738
.....
32739
32740
32741
32742
32743
32744
32745
32746
32747
32748
32749
32750
32751
32752
32753
.....
32758
32759
32760
32761
32762
32763
32764
32765
32766
32767
32768
32769
32770
32771
32772
32773
32774
32775
32776
32777
32778
32779
.....
32983
32984
32985
32986
32987
32988
32989
32990
32991
32992
32993
32994
32995
32996
32997
32998
32999
33000
33001
33002
33003
33004
33005
33006
33007
.....
33023
33024
33025
33026
33027
33028
33029
33030
33031
33032
33033
33034
33035
33036
33037
.....
33053
33054
33055
33056
33057
33058
33059
33060
33061
33062
33063
33064
33065
33066
33067
.....
34091
34092
34093
34094
34095
34096
34097
34098
34099
34100
34101
34102
34103
34104
34105
.....
35113
35114
35115
35116
35117
35118
35119





35120
35121
35122
35123
35124
35125
35126
.....
35307
35308
35309
35310
35311
35312
35313





35314
35315
35316
35317
35318
35319
35320
.....
36752
36753
36754
36755
36756
36757
36758











































































































36759
36760
36761
36762
36763
36764
36765
.....
37271
37272
37273
37274
37275
37276
37277
37278
37279
37280
37281
37282
37283
37284
37285
37286
37287
37288
37289
37290
37291
37292
37293
37294
37295
37296
37297
37298
37299
37300
37301
37302
37303
37304
37305
37306
37307
37308
37309
37310
37311
37312
37313
37314
37315
37316
37317
37318
37319
37320
37321
37322
37323
37324
37325
37326
37327
37328
37329
37330
37331
37332
37333
37334
37335
37336
37337
37338
37339
37340
37341
37342
37343
37344
37345
37346
37347
37348
37349
37350
37351
37352
37353
37354
37355
37356
37357
37358
37359
37360
37361
37362
37363
37364
37365
37366
37367
37368
37369
37370
37371
37372
37373
37374
37375
37376
37377
37378
37379
37380
37381
37382
37383
37384
37385
37386
37387
37388
37389
37390
37391
37392
37393
37394
37395
37396
37397
37398
37399
37400
37401
37402
37403
37404
37405
37406
37407
37408
37409
37410
37411
37412
37413
37414
37415
37416
37417
37418
37419
37420
37421
37422
37423
37424
37425
37426
37427
37428
37429
37430
37431
37432
37433
37434
37435
37436
37437
37438
37439
37440
37441
37442
37443
37444
37445
37446
37447
37448
37449
37450
37451
37452
37453
37454
37455
37456
37457
37458
37459
37460
37461
37462
37463
37464
37465
37466
37467
37468
37469
37470
37471
37472
37473
37474
37475
37476
37477
37478
37479
37480
37481
37482
37483
37484
37485
37486
37487
37488
37489
37490
37491
37492
37493
37494
37495
37496
37497
37498
37499
37500
37501
37502
37503
37504
37505
37506
37507
37508
37509
37510
37511
37512
37513
37514
37515
37516
37517
37518
37519
37520
37521
37522
37523
37524
37525
37526
37527
37528
37529
37530
37531
37532
37533
37534
37535
37536
37537
37538
37539
37540
37541
37542
37543
37544
37545
37546
37547
37548
37549
37550
37551
37552
37553
37554
37555
37556
37557
37558
37559
37560
37561
37562
37563
37564
37565
37566
37567
37568
37569
37570
37571
37572
37573
37574
37575
37576
37577
37578
37579
37580
37581
37582
37583
37584
37585
37586
37587
37588
37589
37590
37591
37592
37593
37594
37595
37596
37597
37598
37599
37600
37601
37602
37603
37604
37605
37606
37607
37608
37609
37610
37611
37612
37613
37614
37615
37616
37617
37618
37619
37620
37621
37622
37623
37624
37625
37626
37627
37628
37629
37630
37631
37632
37633
37634
37635
37636
37637
37638
37639
37640
37641
37642
37643
37644
37645
37646
37647
37648
37649
37650
37651
37652
37653
37654
37655
37656
37657
37658
37659
37660
37661
37662
37663
37664
37665
37666
37667
37668
37669
37670
37671
37672
37673
37674
37675
37676
37677
37678
37679
37680
37681
37682
37683
37684
37685
37686
37687
37688
37689
37690
37691
37692
37693
37694
37695
37696
37697
37698
37699
37700
37701
37702
37703
37704
37705
37706
37707
37708
37709
37710
37711
37712
37713
37714
37715
37716
37717
37718
37719
37720
37721
37722
37723
37724
37725
37726
37727
37728
37729
37730
37731
37732
37733
37734
37735
37736
37737
37738
37739
37740
37741
37742
37743
37744
37745
37746
37747
37748
37749
37750
37751
37752
37753
37754
37755
37756
37757
37758
37759
37760
37761
37762
37763
37764
37765
37766
37767
37768
37769
37770
37771
37772
37773
37774
37775
37776
37777
37778
37779
37780
37781
37782
37783
37784
37785
37786
37787
37788
37789
37790
37791
37792
37793
37794
37795
37796
37797
37798
37799
37800
37801
37802
37803
37804
37805
37806
37807
37808
37809
37810
37811
37812
37813
37814
37815
37816
37817
37818
37819
37820
37821
37822
37823
37824
37825
37826
37827
37828
37829
37830
37831
37832
37833
37834
37835
37836
37837
37838
37839
37840
37841
37842
37843
37844
37845
37846
37847
37848
37849
37850
37851
37852
37853
37854
37855
37856
37857
37858
37859
37860
37861
37862
37863
37864
37865
37866
.....
39384
39385
39386
39387
39388
39389
39390




















39391
39392
39393
39394
39395
39396
39397
.....
42170
42171
42172
42173
42174
42175
42176
42177
42178
42179
42180
42181
42182
42183
42184
42185
42186
42187
42188
.....
42189
42190
42191
42192
42193
42194
42195











42196
42197
42198
42199
42200
42201
42202
.....
42492
42493
42494
42495
42496
42497
42498
42499
42500
42501
42502
42503
42504
42505
42506
42507
42508
42509
42510
42511
42512
42513
42514
42515
42516
42517
42518
42519
42520
42521
42522
42523
42524
42525
42526
42527
42528
42529
42530
42531
42532
42533
42534
42535
42536
42537
42538
42539
42540
42541
42542
42543
42544
42545
42546
42547
42548
42549
42550
42551
42552
42553
42554
42555
42556
42557
42558
42559
42560
42561
42562
42563
42564
42565
42566
42567
42568
42569
42570
42571
42572
42573
42574
42575
42576
42577
42578
42579
42580
42581
42582
42583
42584
42585
42586
42587
42588
42589
42590
42591
42592
42593
42594
42595
42596
42597
42598
42599
42600
42601
42602
42603
42604
42605
42606
42607
42608
42609
42610
42611
42612
42613
42614
42615
42616
42617
42618
42619
42620
42621
42622
42623
42624
42625
42626
42627
42628
42629
42630
42631
42632
42633
42634
42635
42636
42637
42638
42639
42640
42641
42642
42643
42644
42645
42646
42647
42648
42649
42650
42651
42652
42653
42654
42655
42656
42657
42658
42659
42660
42661
42662
42663
42664
42665
42666
42667
42668
42669
42670
42671
42672
42673
42674
42675
42676
42677
42678
42679
42680
42681
42682
42683
42684
42685
42686
42687
42688
42689
42690
42691
42692
42693
42694
42695
42696
42697
42698
42699
42700
42701
42702
42703
42704
42705
42706
42707
42708
42709
42710
42711
42712
42713
42714
42715
42716
42717
42718
42719
42720
42721
42722
42723
42724
42725
42726
42727
42728
42729
42730
42731
42732
42733
42734
42735
42736
42737
42738
42739
42740
42741
42742
42743
42744
42745
42746
42747
42748
42749
42750
42751
42752
42753
42754
42755
42756
42757
42758
42759
42760
42761
42762
42763
42764
42765
42766
42767
42768
42769
42770
42771
42772
42773
42774
42775
42776
42777
42778
42779
42780
42781
42782
42783
42784
42785
42786
42787
42788
42789
42790
42791
42792
42793
42794
42795
42796
42797
42798
42799
42800
42801
42802
42803
42804
42805
42806
42807
42808
42809
42810
42811
42812
42813
42814
42815
42816
42817
42818
42819
42820
42821
42822
42823
42824
42825
42826
42827
42828
42829
42830
42831
42832
42833
42834
42835
42836
42837
42838
42839
42840
42841
42842
42843
42844
42845
42846
42847
42848
42849
42850
42851
42852
42853
42854
42855
42856
42857
42858
42859
42860
42861
42862
42863
42864
42865
42866
42867
42868
42869
42870
42871
42872
42873
42874
42875
42876
42877
42878
42879
42880
42881
42882
42883
42884
42885
42886
42887
42888
42889
42890
42891
42892
42893
42894
42895
42896
42897
42898
42899
42900
42901
42902
42903
42904
42905
42906
42907
42908
42909
42910
42911
42912
42913
42914
42915
42916
42917
42918
42919
42920
42921
42922
42923
42924
42925
42926
42927
42928
42929
42930
42931
42932
42933
42934
42935
42936
42937
42938
42939
42940
42941
42942
42943
42944
42945
42946
42947
42948
42949
42950
42951
42952
42953
42954
42955
42956
42957
42958
42959
42960
42961
42962
42963
42964
42965
42966
42967
42968
42969
42970
42971
42972
42973
42974
42975
42976
42977
42978
42979
42980
42981
42982
42983
42984
42985
42986
42987
42988
42989
42990
42991
42992
42993
42994
42995
42996
42997
42998
42999
43000
43001
43002
43003
43004
43005
43006
43007
43008
43009
43010
43011
43012
43013
43014
43015
43016
43017
43018
43019
43020
43021
43022
43023
43024
43025
43026
43027
43028
43029
43030
43031
43032
43033
43034
43035
43036
43037
43038
43039
43040
43041
43042
43043
43044
43045
43046
43047
43048
43049
43050
43051
43052
43053
43054
43055
43056
43057
43058
43059
43060
43061
43062
43063
43064
43065
43066
43067
43068
43069
43070
43071
43072
43073
43074
43075
43076
43077
43078
43079
43080
43081
43082
43083
43084
43085
43086
43087
43088
43089
43090
43091
43092
43093
43094
43095
43096
43097
43098
43099
43100
43101
43102
43103
43104
43105
43106
43107
43108
43109
43110
43111
43112
43113
43114
43115
43116
43117
43118
43119
43120
43121
43122

43123
43124
43125
43126
43127
43128
43129
43130
43131
43132
43133
43134
43135
43136
43137
43138
43139
43140
43141
43142
43143
43144
43145
43146
43147
43148
43149
43150
43151
43152
43153
43154
43155
43156
43157
43158
43159
43160
43161
43162
43163
43164
43165
43166
43167
43168
43169
43170
43171
43172
43173
43174
43175
43176
43177
43178
43179
43180
43181
43182
43183
43184
43185
43186
43187
43188
43189
43190
43191
43192
43193
43194
43195
43196
43197
43198
43199
43200
43201
43202
43203
43204
43205
43206
43207
43208
43209
43210
43211
43212
43213
43214
43215
43216
43217
43218
43219
43220
43221
43222
43223
43224
43225
43226
43227
43228
43229
43230
43231
43232
43233
43234
43235
43236
43237
43238
43239
43240
43241
43242
43243
43244
43245
43246
43247
43248
43249
43250
43251
43252
43253
43254
43255
43256
43257
43258
43259
43260
43261
43262
43263
43264
43265
43266
43267
43268
43269
43270
43271
43272
43273
43274
43275
43276
43277
43278
43279
43280
43281
43282
43283
43284
43285
43286
43287
43288
43289
43290
43291
43292
43293
43294
43295
43296
43297
43298
43299
43300
43301
43302
43303
43304
43305
43306
43307
43308
43309
43310
43311
43312
43313
43314
43315
43316
43317
43318
43319
43320
43321
43322
43323
43324
43325
43326
43327
43328
43329
43330
43331
43332
43333
43334
43335
43336
43337
43338
43339
43340
43341
43342
43343
43344
43345
43346
43347
43348
43349
43350
43351
43352
43353
43354
43355
43356
43357
43358
43359
43360
43361
43362
43363
43364
43365
43366
43367
43368
43369
43370
43371
43372
43373
43374
43375
43376
43377
43378
43379
43380
43381
43382
43383
43384
43385
43386
43387
43388
43389
43390
43391
43392
43393
43394
43395
43396
43397
43398
43399
43400
43401
43402
43403
43404
43405
43406
43407
43408
43409
43410
43411
43412
43413
43414
43415
43416
43417
43418
43419
43420
43421
43422
43423
43424
43425
43426
43427
43428
43429
43430
43431
43432
43433
43434
43435
43436
43437
43438
43439
43440
43441
43442
43443
43444
43445
43446
43447
43448
43449
43450
43451
43452
43453
43454
43455
43456
43457
43458
43459
43460
43461
43462
43463
43464
43465
43466
43467
43468
43469
43470
43471
43472
43473
43474
43475
43476
43477
43478
43479
43480
43481
43482
43483
43484
43485
43486
43487
43488
43489
43490
43491
43492
43493
43494
43495
43496
43497
43498
43499
43500
43501
43502
43503
43504
43505
43506
43507
43508
43509
43510
43511
43512
43513
43514
43515
43516
43517
43518
43519
43520
43521
43522
43523
43524
43525
43526
43527
43528
43529
43530
43531
43532
43533
43534
43535
43536
43537
43538
43539
43540
43541
43542
43543
43544
43545
43546
43547
43548
43549
43550
43551
43552
43553
43554
43555
43556
43557
43558
43559
43560
43561
43562
43563
43564
43565
43566
43567
43568
43569
43570
43571
43572
43573
43574
43575
43576
43577
43578
43579
43580
43581
43582
43583
43584
43585
43586
43587
43588
43589
43590
43591
43592
43593
43594
43595
43596
43597
43598
43599
43600
43601
43602
43603
43604
43605
43606
43607
43608
43609
43610
43611
43612
43613
43614
43615
43616
43617
43618
43619
43620
43621
43622
43623
43624
43625
43626
43627
43628
43629
43630
43631
43632
43633
43634
43635
43636
43637
43638
43639
43640
43641
43642
43643
43644
43645
43646
43647
43648
43649
43650
43651
43652
43653
43654
43655
43656
43657
43658
43659
43660
43661
43662
43663
43664
43665
43666
43667
43668
43669
43670
43671
43672
43673
43674
43675
43676
43677
43678
43679
43680
43681
43682
43683
43684
43685
43686
43687
43688
43689
43690
43691
43692
43693
43694
43695
43696
43697
43698
43699
43700
43701
43702
43703
43704
43705
43706
43707
43708
43709
43710
43711
43712
43713
43714
43715
43716
43717
43718
43719
43720
43721
43722
43723
43724
43725
43726
43727
43728
43729
43730
43731
43732
43733
43734
43735
43736
43737
43738
43739
43740
43741
43742
43743
43744
43745
43746
43747
43748
43749
43750
43751
43752
43753
43754
43755
43756
43757
43758
43759
43760
43761
43762
43763
43764
43765
43766
43767
43768
43769
43770
43771
43772
43773
43774
43775
43776
43777
43778
43779
43780
43781
43782
43783
43784
43785
43786
43787
43788
43789
43790
43791
43792
43793
43794
43795
43796
43797
43798
43799
43800
43801
43802
43803
43804
43805
43806
43807
43808
43809
43810
43811
43812
43813
43814
43815
43816
43817
43818
43819
43820
43821
43822
43823
43824
43825
43826
43827
43828
43829
43830
43831
43832
43833
43834
43835
43836
43837
43838
43839
43840
43841
43842
43843
43844
43845
43846
43847
43848
43849
43850
43851
43852
43853
43854
43855
43856
43857
43858
43859
43860
43861
43862
43863
43864
43865
43866
43867
43868
43869
43870
43871
43872
43873
43874
43875
43876
43877
43878
43879
43880
43881
43882
43883
43884
43885
43886
43887
43888
43889
43890
43891
43892
43893
43894
43895
43896
43897
43898
43899
43900
43901
43902
43903
43904
43905
43906
43907
43908
43909
43910
43911
43912
43913
43914
43915
43916
43917
43918
43919
43920
43921
43922
43923
43924
43925
43926
43927
43928
43929
43930
43931
43932
43933
43934
43935
43936
43937
43938
43939
43940
43941
43942
43943
43944
43945
43946
43947
43948
43949
43950
43951
43952
43953
43954
43955
43956
43957
43958
43959
43960
43961
43962
43963
43964
43965
43966
43967
43968
43969
43970
43971
43972
43973
43974
43975
43976
43977
43978
43979
43980
43981
43982
43983
43984
43985
43986
43987
43988
43989
43990
43991
43992
43993
43994
43995
43996
43997
43998
43999
44000
44001
44002
44003
44004
44005
44006
44007
44008
44009
44010
44011
44012
44013
44014
44015
44016
44017
44018
44019
44020
44021
44022
44023
44024
44025
44026
44027
44028
44029
44030
44031
44032
44033
44034
44035
44036
44037
44038
44039
44040
44041
44042
44043
44044
44045
44046
44047
44048
44049
44050
44051
44052
44053
44054
44055
44056
44057
44058
44059
44060
44061
44062
44063
44064
44065
44066
44067
44068
44069
44070
44071
44072
44073
44074
44075
44076
44077
44078
44079
44080
44081
44082
44083
44084
44085
44086
44087
44088
44089
44090
44091
44092
44093
44094
44095
44096
44097
44098
44099
44100
44101
44102
44103
44104
44105
44106
44107
44108
44109
44110
44111
44112
44113
44114
44115
44116
44117
44118
44119
44120
44121
44122
44123
44124
44125
44126
44127
44128
44129
44130
44131
44132
44133
44134
44135
44136
44137
44138
44139
44140
44141
44142
44143
44144
44145
44146
44147
44148
44149
44150
44151
44152
44153
44154
44155
44156
44157
44158
44159
44160
44161
44162
44163
44164
44165
44166
44167
44168
44169
44170
44171
44172
44173
44174
44175
44176
44177
44178
44179
44180
44181
44182
44183
44184
44185
44186
44187
44188
44189
44190
44191
44192
44193
44194
44195
44196
44197
44198
44199
44200
44201
44202
44203
44204
44205
44206
44207
44208
44209
44210
44211
44212
44213
44214
44215
44216
44217
44218
44219
44220
44221
44222
44223
44224
44225
44226
44227
44228
44229
44230
44231
44232
44233
44234
44235
44236
44237
44238
44239
44240
44241
44242
44243
44244
44245
44246
44247
44248
44249
44250
44251
44252
44253
44254
44255
44256
44257
44258
44259
44260
44261
44262
44263
44264
44265
44266
44267
44268
44269
44270
44271
44272
44273
44274
44275
44276
44277
44278
44279
44280
44281
44282
44283
44284
44285
44286
44287
44288
44289
44290
44291
44292
44293
44294
44295
44296
44297
44298
44299
44300
44301
44302
44303
44304
44305
44306
44307
44308
44309
44310
44311
44312
44313
44314
44315
44316
44317
44318
44319
44320
44321
44322
44323
44324
44325
44326
44327
44328
44329
44330
44331
44332
44333
44334
44335
44336
44337
44338
44339
44340
44341
44342
44343
44344
44345
44346
44347
44348
44349
44350
44351
44352
44353
44354
44355
44356
44357
44358
44359
44360
44361
44362
44363
44364
44365
44366
44367
44368
44369
44370
44371
44372
44373
44374
44375
44376
44377
44378
44379
44380
44381
44382
44383
44384
44385
44386
44387
44388
44389
44390
44391
44392
44393
44394
44395
44396
44397
44398
44399
44400
44401
44402
44403
44404
44405
44406
44407
44408
44409
44410
44411
44412
44413
44414
44415
44416
44417
44418
44419
44420
44421
44422
44423
44424
44425
44426
44427
44428
44429
44430
44431
44432
44433
44434
44435
44436
44437
44438
44439
44440
44441
44442
44443
44444
44445
44446
44447
44448
44449
44450
44451
44452
44453
44454
44455
44456
44457
44458
44459
44460
44461
44462
44463
44464
44465
44466
44467
44468
44469
44470
44471
44472
44473
44474
44475
44476
44477
44478
44479
44480
44481
44482
44483
44484
44485
44486
44487
44488
44489
44490
44491
44492
44493
44494
44495
44496
44497
44498
44499
44500
44501
44502
44503
44504
44505
44506
44507
44508
44509
44510
44511
44512
44513
44514
44515
44516
44517
44518
44519
44520
44521
44522
44523
44524
44525
44526
44527
44528
44529
44530
44531
44532
44533
44534
44535
44536
44537
44538
44539
44540
44541
44542
44543
44544
44545
44546
44547
44548
44549
44550
44551
44552
44553
44554
44555
44556
44557
44558
44559
44560
44561
44562
44563
44564
44565
44566
44567
44568
44569
44570
44571
44572
44573
44574
44575
44576
44577
44578
44579
44580
44581
44582
44583
44584
44585
44586
44587
44588
44589
44590
44591
44592
44593
44594
44595
44596
44597
44598
44599
44600
44601
44602
44603
44604
44605
44606
44607
44608
44609
44610
44611
44612
44613
44614
44615
44616
44617
44618
44619
44620
44621
44622
44623
44624
44625
44626
44627
44628
44629
44630
44631
44632
44633
44634
44635
44636
44637
44638
44639
44640
44641
44642
44643
44644
44645
44646
44647
44648
44649
44650
44651
44652
44653
44654
44655
44656
44657
44658
44659
44660
44661
44662
44663
44664
44665
44666
44667
44668
44669
44670
44671
44672
44673
44674
44675
44676
44677
44678
44679
44680
44681
44682
44683
44684
44685
44686
44687
44688
44689
44690
44691
44692
44693
44694
44695
44696
44697
44698
44699
44700
44701
44702
44703
44704
44705
44706
44707
44708
44709
44710
44711
44712
44713
44714
44715
44716
44717
44718
44719
44720
44721
44722
44723
44724
44725
44726
44727
44728
44729
44730
44731
44732
44733
44734
44735
44736
44737
44738
44739
44740
44741
44742
44743
44744
44745
44746
44747
44748
44749
44750
44751
44752
44753
44754
44755
44756
44757
44758
44759
44760
44761
44762
44763
44764
44765
44766
44767
44768
44769
44770
44771
44772
44773
44774
44775
44776
44777
44778
44779
44780
44781
44782
44783
44784
44785
44786
44787
44788
44789
44790
44791
44792
44793
44794
44795
44796
44797
44798
44799
44800
44801
44802
44803
44804
44805
44806
44807
44808
44809
44810
44811
44812
44813
44814
44815
44816
44817
44818
44819
44820
44821
44822
44823
44824
44825
44826
44827
44828
44829
44830
44831
44832
44833
44834
44835
44836
44837
44838
44839
44840
44841
44842
44843
44844
44845
44846
44847
44848
44849
44850
44851
44852
44853
44854
44855
.....
44922
44923
44924
44925
44926
44927
44928
44929
44930
44931
44932
44933
44934
44935
44936
.....
45064
45065
45066
45067
45068
45069
45070
45071
45072
45073
45074
45075
45076
45077
45078
45079
45080
45081
45082
45083
45084
45085
45086
45087
45088
45089
45090
45091
45092
45093
45094
45095
45096
45097
45098
45099
45100
45101
45102
45103
45104
45105
45106
45107
45108
45109
45110
45111
45112
45113
45114
45115
45116
45117
45118
45119
45120
45121
45122
45123
45124
45125
45126
45127
45128
45129
45130
45131
45132
45133
45134
45135
45136
45137
45138
45139
45140
45141
45142
45143
45144
45145
45146
45147
45148
45149
45150
45151
45152
45153
45154
45155
45156
45157
45158
45159
45160
45161
45162
45163
45164
45165
45166
45167
45168
45169
45170
45171
45172
45173
45174
45175
45176
45177
45178
45179
45180
45181
45182
45183
45184
45185
45186
45187
45188
45189
45190
45191
45192
45193
45194
45195
45196
45197
45198
45199
45200
45201
45202
45203
45204
45205
45206
45207
45208
45209
45210
45211
45212
45213
45214
45215
45216
45217
45218
45219
45220
45221
45222
45223
45224
45225
45226
45227
45228
45229
45230
45231
45232
45233
45234
45235
45236
45237
45238
45239
45240
45241
45242
45243
45244
45245
45246
45247
45248
45249
45250
45251
45252
45253
45254
45255
45256
45257
45258
45259
45260
45261
45262
45263
45264
45265
45266
45267
45268
45269
45270
45271
45272
45273
45274
45275
45276
45277
45278
45279
45280
45281
45282
45283
45284
45285
45286
45287
45288
45289
45290
45291
45292
45293
45294
45295
45296
45297
45298
45299
45300
45301
45302
45303
45304
45305
45306
45307
45308
45309
45310
45311
45312
45313
45314
45315
45316
45317
45318
45319
45320
45321
45322
45323
45324
45325
45326
45327
45328
45329
45330
45331
45332
45333
45334
45335
45336
45337
45338
45339
45340
45341
45342
45343
45344
45345
45346
45347
45348
45349
45350
45351
45352
45353
45354
45355
45356
45357
45358
45359
45360
45361
45362
45363
45364
45365
45366
45367
45368
45369
45370
45371
45372
45373
45374
45375
45376
45377
45378
45379
45380
45381
45382
45383
45384
45385
45386
45387
45388
45389
45390
45391
45392
45393
45394
45395
45396
45397
45398
45399
45400
45401
45402
45403
45404
45405
45406
45407
45408
45409
45410
45411
45412
45413
45414
45415
45416
45417
45418
45419
45420
45421
45422
45423
45424
45425
45426
45427
45428
45429
45430
45431
45432
45433
45434
45435
45436
45437
45438
45439
45440
45441
45442
45443
45444
45445
45446
45447
45448
45449
45450
45451
45452
45453
45454
45455
45456
45457
45458
45459
45460
45461
45462
45463
45464
45465
45466
45467
45468
45469
45470
45471
45472
45473
45474
45475
45476
45477
45478
45479
45480
45481
45482
45483
45484
45485
45486
45487
45488
45489
45490
45491
45492
45493
45494
45495
45496
45497
45498
45499
45500
45501
45502
45503
45504
45505
45506
45507
45508
45509
45510
45511
45512
45513
45514
45515
45516
45517
45518
45519
45520
45521
45522
45523
45524
45525
45526
45527
45528
45529
45530
45531
45532
45533
45534
45535
45536
45537
45538
45539
45540
45541
45542
45543
45544
45545
45546
45547
45548
45549
45550
45551
45552
45553
45554
45555
45556
45557
45558
45559
45560
45561
45562
45563
45564
45565
45566
45567
45568
45569
45570
45571
45572
45573
45574
45575
45576
45577
45578
45579
45580
45581
45582
45583
45584
45585
45586
45587
45588
45589
45590
45591
45592
45593
45594
45595
45596
45597
45598
45599
45600
45601
45602
45603
45604
45605
45606
45607
45608
45609
45610
45611
45612
45613
45614
45615
45616
45617
45618
45619
45620
45621
45622
45623
45624
45625
45626
45627
45628
45629
45630
45631
45632
45633
45634
45635
45636
45637
45638
45639
45640
45641
45642
45643
45644
45645
45646
.....
45773
45774
45775
45776
45777
45778
45779
45780
45781
45782
45783
45784
45785
45786
45787
.....
46179
46180
46181
46182
46183
46184
46185




















46186
46187
46188
46189
46190
46191
46192
  - fixed implementation issue in function FindNextUTF8WordBegin()
  - fixed false negative issue in TSynSoundEx.UTF8 and TSynSoundEx.Ansi
  - fixed wrong UTF-8 encoding of U+FFF0 used for JSON_BASE64_MAGIC
  - added an optional parameter to StrToCurr64() function, able to return
    a true Int64 value if no decimal is supplied within the input text buffer
  - enhanced TSynAnsiFixedWidth.UnicodeBufferToAnsi average process speed
  - TSynCache.Reset now returns a boolean stating if something was flushed
  - new SynUnicodeToUtf8(), ShortStringToUTF8(), StringToSynUnicode(), 
    SynUnicodeToString() functions
  - new StrToCurrency() wrapper function
  - new IdemPropName() overloaded function with two PUTF8Char arguments
  - new UTF8UpperCopy() and UTF8UpperCopy255() optimized functions
  - new GotoNextNotSpace() and GotoEndOfQuotedString() functions
  - new TMemoryMap.Map() method expecting a file name as parameter
  - new TRawUTF8List.LoadFromFile() method
................................................................................
    even after true/false/null, as expected by the specification
  - fixed potential Integer Overflow error in Iso8601ToDateTimePUTF8Char*()
  - added PatchCode() and RedirectCodeRestore() procedures, and some code
    refactoring about process in-memory code patching
  - internal FillChar() will now use faster SSE2 instructions on supported CPUs

  Version 1.18

  - BREAKING CHANGE of TTextWriter.WriteObject() method: serialization is now
    defined with a new TTextWriterWriteObjectOptions set
  - BREAKING CHANGE rename of Iso8601 low-level structure as TTimeLogBits, to use
    explicitly the TTimeLog type and name for all Int64 bit-oriented functions -
    now "Iso8601" naming will be only for standard ISO-8601 text, not Int64 value
  - Delphi XE4/XE5/XE6/XE7 compatibility (Windows target platform only)
  - unit fixed and tested with Delphi XE2 (and up) 64-bit compiler under Windows
................................................................................
    UTF8ToString(aVariant) if you want to use the value with the VCL
  - introducing TDocVariant for variant-based process of any hierarchy
    of objects and/or arrays, with late binding optimized access and JSON
    serialization/unserialization (will also be used for BSON documents storage)
  - UTF-8 process will now handle UTF-16 surrogates - see ticket [4a0382367d] -
    UnicodeCharToUTF8/NextUTF8Char are renamed WideCharToUTF8/NextUTF8UCS4 and
    new UTF16CharToUTF8/UCS4ToUTF8 functions have been introduced



  - StrLen() function will now use faster SSE2 instructions on supported CPUs
  - introduced StrLenPas() function, to be used when buffer is protected
  - included Windows-1258 code page to be recognized as a fixed-width charset
  - TSynAnsiFixedWidth.Create(CODEPAGE_US) will now use a hard-coded table, 
    since some Russian system do tweak the registry to force 1252 page maps 1251
  - introducing TSynAnsiUTF8/TSynAnsiUTF16 to handle CP_UTF8/CP_UTF16 codepages
  - added UTF8AnsiConvert instance, and let TSynAnsiConvert.Engine(0) return
................................................................................
  - introducing TObjectDynArrayWrapper class and IObjectDynArray interface
  - introducing TSynAuthentication class for simple generic authentication
  - added TDynArrayHashed.HashElement property
  - new TDynArrayHashed.AddUniqueName() method
  - introduced TSingleDynArray, recognized as such in JSON serialization
  - added WordScanIndex() and swap32() functions
  - speed improvement of IdemPropNameU() function, with new overload function
  - added TSynLogFile.Freq read-only property
  - added TMemoryMapText.AddInMemoryLine method to allow runtime appending of
    new lines of text - used e.g. by TSynLogFile for life update of remote logs
  - added TMemoryMapText.SaveToFile() and TMemoryMapText.SaveToStream() methods
  - added DefaultSynLogExceptionToStr() function and TSynLogExceptionToStrCustom
    variable, and ESynException.CustomLog() method to customize how raised
    exception are logged when intercepted - feature request [495720e0b9]
  - added ESynException.CreateUTF8() constructor, more powerful than the
    default Exception.CreateFmt(): this CreateUTF8 method is now used everywhere
  - now FileSize() function won't raise any exception if the file does not exist
    and will return any size > 2 GB as expected
  - faster PosEx() function in pure pascal mode (based on Avatar Zondertau work)
  - added StringDynArrayToRawUTF8DynArray() and StringListToRawUTF8DynArray()
  - added CSVToRawUTF8DynArray() overloaded functions
  - added GetLastCSVItem() function and dedicated HashPointer() function
  - added DirectoryDelete() and EnsureDirectoryExists() function
................................................................................
  - added faster TTextWriter.SetText() method in conjuction to Text function
  - added TTextWriter.Add(const guid: TGUID) overloaded method
  - TTextWriter.Add(Format..) will now ignore any character afer |, i.e. |$ = $
  - added TTextWriter.AddQuotedStr() and AddStringCopy() methods
  - added TTextWriter.AddVoidRecordJSON() method
  - added TTextWriter.AddJSONEscapeAnsiString() method
  - added TTextWriter.AddAnyAnsiString() method and AnyAnsiToUTF8() function
  - added TTextWriter.EndOfLineCRLF and TSynLogFamily.EndOfLineCRLF properties   
  - for Delphi 2010 and up, RecordSaveJSON/RecordLoadJSON will use enhanced RTTI
  - before Delphi 2010, you can specify the record layout as text to
    TTextWriter.RegisterCustomJSONSerializerFromText() for JSON serialization
  - added TTextWriter.RegisterCustomJSONSerializerSetOptions() for [da22968223]
  - added TTextWriter.AddDynArrayJSON() overloaded method and new functions
    DynArrayLoadJSON() and DynArraySaveJSON() to be used e.g. for custom
    record JSON serialization, using TDynArrayJSONCustomReader/Writer
................................................................................
    callbacks and/or RegisterCustomJSONSerializerFromText(), or enhanced RTTI
  - added TTextWriter.UnRegisterCustomJSONSerializer() method
  - added TTextWriter.RegisterCustomJSONSerializerFromTextSimpleType() method
  - added TTextWriter.AddTypedJSON() and AddCRAndIdent methods
  - added TJSONWriter.EndJSONObject() method, for writing an optional
    ',"rowCount":' field in non expanded mode - used for all JSON creation
  - added TTextWriter.EchoAdd() and EchoRemove() methods
  - added TSynLogFamily's NoFile and EchoCustom properties - see [91a114d2f6]
  - added MultiEventAdd(), MultiEventRemove() and MultiEventFind() functions,
    allowing easy implementation of a dynamic list of callbacks
  - added QuickSortIndexedPUTF8Char() and FastFindIndexedPUTF8Char()
  - added overloaded QuickSortInteger() for synchronous sort of two arrays
  - added GetNextItem64() Int64Scan() Int64ScanExists() QuickSortInt64()
    FastFindInt64Sorted() AddInt64() CSVToInt64DynArray() Int64DynArrayToCSV()
    and VariantToInt64() functions (used during TID=Int64 introduction in ORM)
  - added RawUnicodeToUtf8() and UTF8ToSynUnicode() overloaded procedures
  - added UrlDecodeNextValue() and UrlDecodeNextNameValue() functions
  - added Utf8DecodeToRawUnicodeUI() overloaded function returning text as var
  - added class function TSynTestCase.RandomTextParagraph
  - added UrlEncodeJsonObject() and new overloaded JSONDecode() function
  - added TRawUTF8DynArrayFrom(const Values: array of RawUTF8) function
  - added overloaded function FindRawUTF8() using array of RawUTF8 parameter
  - added TPropNameList record/object to maintain a stack-based list of names
  - speeed enhancement for TRawUTF8List.Add()
  - added optional aOwnObjects parameter to TRawUTF8List.Create() constructor
  - new TRawUTF8List.GetObjectByName() method
................................................................................
  - now TFileBufferReader.Read() allows forward reading when Data=nil
  - added RecordSaveJSON() function which follows TTextWriter.AddRecordJSON() format
  - added TSynNameValue.InitFromIniSection() method and optional default value
    parameter to TSynNameValue.Value()
  - added TSynNameValue.Delete() and SetBlobDataPtr() methods
  - added TSynNameValue.OnAfterAdd callback event
  - added TObjectListLocked class
  - TSynTests will now write the tests summary with colored console output
  - added TSynTestCase.CleanUp virtual method for proper cleaning before Destroy
  - added TSynTestCase.CheckMatchAny() method for multi-value checks
  - TSynTestCase.TestFailed now triggers a debugger breakpoint when run from IDE
  - added TSynTestCase.NotifyTestSpeed() method
  - TSynLog will now append only the execution time when leaving a method,
    without the class/method name (smaller log file, and less resource use)
  - TSynLog header now contains system environment variables
  - added TSynLog.DebuggerNotify() and TSynLog.CloseLogFile / Release methods
  - protected the global TSynLog instances list against potential race condition
  - introducing TSynLogFamily.StackTraceUse: TSynLogStackTraceUse property
    (set to stManualAndAPI by default, but stOnlyAPI within the Delphi IDE)
  - introducing TSynLogFamily.EchoToConsole: TSynLogInfos property, able to
    optionally echo the process log to the current console window, using colors
  - added TSynLogFamily.EchoRemoteStart() and EchoRemoteStop methods
  - added TSynLog.Void class function
  - if new property TSynLogFamily.PerThreadLog is set to ptIdentifiedInOnFile,
    a new column will be added for each logged row - LogViewer has been updated
    to allow easy and efficient multi-thread process logging
  - introducing TSynLogFamily.RotateFileCount and associated RotateFileSizeKB,
    RotateFileDailyAtHour and OnRotate properties, to enable log file rotation 
    by size or at given hour - request [72feb66d45] + [b3e8cc8424]
  - added TSynLog.CustomFileName property - see [d8fbc10bf8]
  - added TSynLog.ComputeFileName virtual method and TSynLogFamily.FileExistsAction
    property for feature request [d029051dcb]
  - added TSynLog/ISynLog.LogLines() method for direct multi-line text logging
  - added optional TextTruncateAtLength parameter for TSynLog/ISynLog.Log()
  - declared TSynLog.LogInternal() methods as virtual - request [e47c64fb2c]
  - .NET/CLR external exceptions will now be logged with their C# type name
  - special 'SetThreadName' exception will now be ignored by TSynLog hook
  - expose all internal Hash*() functions (following TDynArrayHashOne prototype)
    in interface section of the unit
  - added crc32c() function using either optimized unrolled version, or SSE 4.2
    instruction: crc32cfast() is 1.7 GB/s, crc32csse42() is 3.7 GB/s
  - added fnv32() function, slower than kr32, but with less collisions
  - added SynLZCompress/SynLZDecompress functions, using crc32c() for hashing
  - added SymmetricEncrypt() function
................................................................................
    (WideString aka OleStr do store their length in bytes, not WideChars)
  - fixed TDynArray.AddArray() method when Count parameter is not specified
  - fixed ticket [ad55566b10] about JSON string escape parsing
  - fixed ticket [cce54e98ca], [388c2768b6] and [355249a9d1] about overflow in
    TTextWriter.AddJSONEscapeW()
  - fixed ticket [a75c0c6759] about TTextWriter.AddNoJSONEscapeW()
  - added TTextWriter.AddHtmlEscape() and TTextWriter.AddXmlEscape() methods
  - new TTextWriter.AddHtmlEscapeWiki() method, supporting wiki-like syntax 
  - TTextWriter.AddJSONEscape/AddJSONEscapeW methods speed up
  - fixed ticket [19e567b8ca] about TSynLog issue in heavily concurrent mode:
    now a per-thread context will be stored, e.g. for Enter/Leave tracking
  - fixed ticket [a516b1a954] about ptOneFilePerThread log file rotation
  - fixed ticket [01408fd389] in TRawUTF8List.GetText()
  - fixed ticket [e3ae1005dc] about potential GPF in TRawUTF8List.Delete()
  - fixed ticket [1c940a4437] to avoid negative value in TPrecisionTimer.PerSec,
    in case of incorrect Start/Stop methods sequence
  - implement ticket [e3f9742865] for enhanced JSON in soWriteHumanReadable mode
  - added TPrecisionTimer.ProfileCurrentMethod() and TimeInUniSec property
    for feature request [1abca090ee]
................................................................................
  - added function GetJSONFieldOrObjectOrArray() in unit's interface section  
  - function GotoNextJSONField() renamed GotoNextJSONItem(), and fixed to
    handle nested JSON array or objects in addition to string/numbers
  - added GotoEndJSONItem() and GetJSONItemAsRawJSON() functions
  - added function JSONRetrieveIDField() for fast retrieval of a "ID":.. value
  - added function JSONRetrieveStringField() for retrieval of a string field
    name or value from JSON buffer
  - added TextColor() and TextBackground() functions - will initialize internal
    console process after any manual AllocConsole call
  - added ConsoleWaitForEnterKey function, able to handle Synchronize() calls
  - added PtrUIntScanIndex() and UnixTimeToDateTime/DateTimeToUnixTime()
    UnixMSTimeToDateTime/DateTimeToUnixMSTime functions
  - fixed ticket [aff1352239] to identify 9999-12-31 dates as valid
  - added Iso8601ToTimePUTF8Char[Var]() and IntervalTextToDateTime[Var]() functions
  - added DateTimeToIso8601ExpandedPChar() and Iso8601CheckAndDecode() functions
  - added TTimeLogBits.FromUTCTime method and NowUTC / TimeLogNowUTC functions
  - added TTimeLogBits.FromUnixTime/FromUnixMSTime/ToUnixTime/ToUnixMSTime
................................................................................
  - fixed potential random GPF in TTextWriter after Flush - see [577ad95cfd0]
  - added TTextWriter.Add(const Values: array of const) method
  - added JSONToXML() JSONBufferToXML() and TTextWriter.JSONBufferToXML()
    for direct and fast conversion of any JSON into the corresponding <XML>
  - added JSONReformat() JSONBufferReformat() and TTextWriter.AddJSONReformat()
    for fast conversion into more readable, compact or extended layout 
  - fixed potential GPF issue in TMemoryMapText.LoadFromMap()



  - allow file size of 0 byte in TMemoryMap.Map()
  - extraction of TTestLowLevelCommon code into SynSelfTests.pas unit
  - introduced TSynInvokeableVariantType.Clear() and Copy() default methods
  - added TSynInvokeableVariantType.CopyByValue() virtual method
  - added TSynInvokeableVariantType.IsOfType() method
  - TSynInvokeableVariantType.SetProperty() will now convert any varOleStr into
    a RawUTF8/varString, and dereference any simple varByRef transmitted values
    so that we could safely use late-binding with any kind of value
  - internal DispInvoke() function speed-up by caching the latest accessed type
................................................................................

/// return true if up^ is contained inside the UTF-8 buffer p^
// - search up^ at the beginning of every UTF-8 word (aka in Soundex)
// - here a "word" is a Win-Ansi word, i.e. '0'..'9', 'A'..'Z'
// - up^ must be already Upper
function ContainsUTF8(p, up: PUTF8Char): boolean;









/// copy source into dest^ with 7 bits upper case conversion
// - returns final dest pointer
// - will copy up to 255 AnsiChar (expect the dest buffer to be array[byte] of
// AnsiChar)
function UpperCopy255(dest: PAnsiChar; const source: RawUTF8): PAnsiChar;

/// copy source into dest^ with WinAnsi 8 bits upper case conversion
................................................................................
function AnsiICompW(u1, u2: PWideChar): PtrInt;

/// SameText() overloaded function with proper UTF-8 decoding
// - fast version using NormToUpper[] array for all Win-Ansi characters
// - this version will decode each UTF-8 glyph before using NormToUpper[]
// - current implementation handles UTF-16 surrogates as UTF8IComp()
function SameTextU(const S1, S2: RawUTF8): Boolean;
  {$ifdef HASINLINE}inline;{$endif} 

/// fast conversion of the supplied text into 8 bit uppercase
// - this will not only convert 'a'..'z' into 'A'..'Z', but also accentuated
// latin characters ('e' acute into 'E' e.g.), using NormToUpper[] array
// - it will decode the supplied UTF-8 content to handle more than
// 7 bit of ascii characters (so this function is dedicated to WinAnsi code page
// 1252 characters set)
................................................................................
  {$ifdef UNDIRECTDYNARRAY}
  TDynArray = record
  private
  {$else}
  TDynArray = object
  protected
  {$endif}
    Value: PPointer;
    fTypeInfo: pointer;
    fElemSize: PtrUInt;
    fElemType: pointer;
    fCompare: TDynArraySortCompare;
    fCountP: PInteger;
    fSorted: boolean;
    fKnownType: TDynArrayKind;
................................................................................
    function GetCount: integer; {$ifdef HASINLINE}inline;{$endif}
    procedure SetCount(aCount: integer);
    function GetCapacity: integer;
    procedure SetCapacity(aCapacity: integer);
    procedure SetCompare(const aCompare: TDynArraySortCompare); {$ifdef HASINLINE}inline;{$endif}
    function FindIndex(const Elem; aIndex: PIntegerDynArray;
      aCompare: TDynArraySortCompare): integer;

    /// will set fKnownType and fKnownOffset/fKnownSize fields
    function ToKnownType: TDynArrayKind;
    /// faster equivalency of System.DynArraySetLength() function
    procedure InternalSetLength(NewLength: PtrUInt);
  public
    /// initialize the wrapper with a one-dimension dynamic array
    // - the dynamic array must have been defined with its own type
................................................................................
    // the compare function
    // - Add/Delete/Insert/Load* methods will reset this property to false
    // - Sort method will set this property to true
    // - you MUST set this property to false if you modify the dynamic array
    // content in your code, so that Find() won't try to use binary search in
    // an usorted array, and miss its purpose
    property Sorted: boolean read fSorted write fSorted;


    /// the known type, possibly retrieved from dynamic array RTTI 
    property KnownType: TDynArrayKind read fKnownType;
    /// the known RTTI information of the whole array
    property ArrayType: pointer read fTypeInfo;


    /// the internal in-memory size of one element, as retrieved from RTTI
    property ElemSize: PtrUInt read fElemSize;
    /// the internal type information of one element, as retrieved from RTTI
    property ElemType: pointer read fElemType;
  end;

  /// function prototype to be used for hashing of an element
................................................................................
  private
    procedure SetCount(aCount: Integer);        inline;
    procedure SetCapacity(aCapacity: Integer);  inline;
    function GetCapacity: Integer;              inline;
  public
    InternalDynArray: TDynArray;
    function Count: Integer;            inline;
    function Value: PPointer;           inline;
    function ElemSize: PtrUInt;         inline;
    function ElemType: Pointer;         inline;
    function KnownType: TDynArrayKind;  inline;
    procedure Clear;                    inline;
    // warning: you shall call ReHash() after manual Add/Delete
    function Add(const Elem): integer;  inline;
    procedure Delete(aIndex: Integer);  inline;
................................................................................
    procedure Flush; virtual;
    /// add a callback to echo each line written by this class
    // - this class expects AddEndOfLine to mark the end of each line
    procedure EchoAdd(const aEcho: TOnTextWriterEcho);
    /// remove a callback to echo each line written by this class
    // - event should have been previously registered by a EchoAdd() call
    procedure EchoRemove(const aEcho: TOnTextWriterEcho);



    /// append one char to the buffer
    procedure Add(c: AnsiChar); overload;
      {$ifdef HASINLINE}inline;{$endif}
    /// append two chars to the buffer
    procedure Add(c1,c2: AnsiChar); overload;
      {$ifdef HASINLINE}inline;{$endif}
................................................................................
    // WriteObject method for full RTTI handling
    // - default implementation will write TList/TCollection/TStrings/TRawUTF8List
    // as appropriate array of class name/pointer (if woFullExpand is set)
    procedure WriteObject(Value: TObject;
      Options: TTextWriterWriteObjectOptions=[woDontStoreDefault]); virtual;
    /// return the last char appended
    function LastChar: AnsiChar;






    /// the last char appended is canceled
    procedure CancelLastChar;
      {$ifdef HASINLINE}inline;{$endif}
    /// the last char appended is canceled if it was a ','
    procedure CancelLastComma;
      {$ifdef HASINLINE}inline;{$endif}
    /// rewind the Stream to the position when Create() was called
................................................................................
    // - if StoreObjectsAsVarUInt32 is TRUE, all Objects[] properties will be
    // stored as VarUInt32
    procedure WriteRawUTF8List(List: TRawUTF8List; StoreObjectsAsVarUInt32: Boolean=false);
    /// append a TStream content
    // - is StreamSize is left as -1, the Stream.Size is used
    // - the size of the content is stored in the resulting stream
    procedure WriteStream(aStream: TCustomMemoryStream; aStreamSize: Integer=-1);







    /// write any pending data in the internal buffer to the file
    // - after a Flush, it's possible to call FileSeek64(aFile,....)
    // - returns the number of bytes written between two FLush method calls
    function Flush: Int64; 
    /// rewind the Stream to the position when Create() was called
    // - note that this does not clear the Stream content itself, just
    // move back its writing position to its initial place
................................................................................
    /// retrieve the current in-memory position
    // - if file was not memory-mapped, returns -1
    function CurrentPosition: integer;
    /// raise an exception in case of invalid content
    procedure ErrorInvalidContent;
    /// read-only access to the global file size
    property FileSize: Int64 read fMap.fFileSize;


  end;


/// FileSeek() overloaded function, working with huge files
// - Delphi FileSeek() is buggy -> use this function to safe access files > 2 GB
// (thanks to sanyin for the report)
function FileSeek64(Handle: THandle; const Offset: Int64; Origin: cardinal): Int64;
................................................................................
const
  /// map a PtrInt type to the TJSONCustomParserRTTIType set
  ptPtrInt  = {$ifdef CPU64}ptInt64{$else}ptInteger{$endif};
  /// map a PtrUInt type to the TJSONCustomParserRTTIType set
  ptPtrUInt = {$ifdef CPU64}ptInt64{$else}ptCardinal{$endif};
  /// which TJSONCustomParserRTTIType types are not simple types
  PT_COMPLEXTYPES = [ptArray, ptRecord, ptCustom];


{ ************ filtering and validation classes and functions }

/// return TRUE if the supplied content is a valid email address
// - follows RFC 822, to validate local-part@domain email format
function IsValidEmail(P: PUTF8Char): boolean;

................................................................................
  public
    /// perform the space triming conversion to the specified value
    procedure Process(aFieldIndex: integer; var Value: RawUTF8); override;
  end;


{ ************ some other common types and conversion routines }






























































/// convert a string into its INTEGER Curr64 (value*10000) representation
// - this type is compatible with Delphi currency memory map with PInt64(@Curr)^
// - fast conversion, using only integer operations
// - if NoDecimal is defined, will be set to TRUE if there is no decimal, AND
// the returned value will be an Int64 (not a PInt64(@Curr)^)
function StrToCurr64(P: PUTF8Char; NoDecimal: PBoolean=nil): Int64;
................................................................................
// to contain the overridden code buffer, for further hook disabling
procedure RedirectCode(Func, RedirectFunc: Pointer; Backup: PPatchCode=nil);

/// self-modifying code - restore a code from its RedirectCode() backup
procedure RedirectCodeRestore(Func: pointer; const Backup: TPatchCode);


type
  /// available console colors under Windows
  TConsoleColor = (
    ccBlack, ccBlue, ccGreen, ccCyan, ccRed, ccMagenta, ccBrown, ccLightGray,
    ccDarkGray, ccLightBlue, ccLightGreen, ccLightCyan, ccLightRed, ccLightMagenta,
    ccYellow, ccWhite);

/// change the Windows console text writing color
// - call this procedure to initialize internal console process, if you manually
// intialized the Windows console, e.g. via the following code:
// ! AllocConsole;
// ! TextColor(ccLightGray);
procedure TextColor(Color: TConsoleColor);

/// change the Windows console text background color
procedure TextBackground(Color: TConsoleColor);

/// will wait for the ENTER key to be pressed, processing the internal
// Windows Message loop and any Synchronize() pending notification
// - to be used e.g. for proper work of console applications with interface-based
// service implemented as optExecInMainThread
procedure ConsoleWaitForEnterKey;


type
  /// to be used instead of TMemoryStream, for speed
  // - allocates memory from Delphi heap (i.e. FastMM4/SynScaleMM)
  // and not GlobalAlloc()
  // - uses bigger growing size of the capacity
{$ifdef LVCL} // LVCL already use Delphi heap instead of GlobalAlloc()
................................................................................
  // - JSON_OPTIONS[false] is e.g. _Json() and _JsonFmt() functions default
  // - JSON_OPTIONS[true] are used e.g. by _JsonFast() and _JsonFastFmt() functions
  JSON_OPTIONS: array[Boolean] of TDocVariantOptions = (
    [dvoReturnNullForUnknownProperty],
    [dvoReturnNullForUnknownProperty,dvoValueCopiedByReference]);

  /// TDocVariant options which may be used for plain JSON parsing
  // - this won't recognize any extended syntax 
  JSON_OPTIONS_FAST_STRICTJSON: TDocVariantOptions =
    [dvoReturnNullForUnknownProperty,dvoValueCopiedByReference,
     dvoJSONParseDoNotTryCustomVariants];

{$endif NOVARIANTS}
































{ ************ Unit-Testing classes and functions }

type
  /// pointer to ta high resolution timer object/record
  PPrecisionTimer = ^TPrecisionTimer;

  /// high resolution timer (for accurate speed statistics)
  // - WARNING: this record MUST be aligned to 32 bit, otherwise iFreq=0 -
................................................................................
    /// resume a paused timer
    procedure Resume;
    /// compute the per second count
    function PerSec(Count: cardinal): cardinal;
    /// compute the time elapsed by count, with appened time resolution (us,ms,s)
    function ByCount(Count: cardinal): RawUTF8;
  end;


{$ifdef MSWINDOWS}
{$ifndef DELPHI5OROLDER}

  /// a simple class which will set FPU exception flags for a code block
  // - using an IUnknown interface to let the compiler auto-generate a
  // try..finally block statement to reset the FPU exception register
  // - to be used e.g. as such:
  // !begin
  // !  TSynFPUException.ForLibrayCode;
  // !  ... now FPU exceptions will be ignored
................................................................................
    /// to be used to compute a Hash on the client, for a given Token
    // - the token should have been retrieved from the server, and the client
    // should compute and return this hash value, to perform the authentication
    // challenge and create the session
    class function ComputeHash(Token: Int64; const UserName,PassWord: RawUTF8): cardinal;
  end;

  /// the prototype of an individual test
  // - to be used with TSynTest descendants
  TSynTestEvent = procedure of object;

{$M+} { we need the RTTI for the published methods of this object class }
  /// a generic class for both tests suit and cases
  // - purpose of this ancestor is to have RTTI for its published methods,
  // and to handle a class text identifier, or uncamelcase its class name
  // if no identifier was defined
  // - sample code about how to use this test framework is available in
  // the "Sample\07 - SynTest" folder
  // - see @http://synopse.info/forum/viewtopic.php?pid=277
  TSynTest = class
  protected
    fTests: array of record
      TestName: string;
      TestNameUTF8: RawUTF8;
      Method: TSynTestEvent;
    end;
    fIdent: string;
    fInternalTestsCount: integer;
    function GetTestName(Index: integer): string; {$ifdef HASINLINE}inline;{$endif}
    function GetTestMethod(Index: integer): TSynTestEvent; {$ifdef HASINLINE}inline;{$endif}
    function GetCount: Integer;
    function GetIdent: string;
  public
    /// create the test instance
    // - if an identifier is not supplied, the class name is used, after
    // T[Syn][Test] left trim and un-camel-case
    // - this constructor will add all published methods to the internal
    // test list, accessible via the Count/TestName/TestMethod properties
    constructor Create(const Ident: string = '');
    /// register a specified test to this class instance
    procedure Add(aMethod: TSynTestEvent; const aName: string);
    /// the test name
    // - either the Ident parameter supplied to the Create() method, either
    // a uncameled text from the class name
    property Ident: string read GetIdent;
    /// return the number of tests associated with this class
    // - i.e. the number of registered tests by the Register() method PLUS
    // the number of published methods defined within this class
    property Count: Integer read GetCount;
    /// get the name of a specified test
    // - Index range is from 0 to Count-1 (including)
    property TestName[Index: integer]: string read GetTestName;
    /// get the event of a specified test
    // - Index range is from 0 to Count-1 (including)
    property TestMethod[Index: integer]: TSynTestEvent read GetTestMethod;
    /// return the number of published methods defined within this class as tests
    // - i.e. the number of tests added by the Create() constructor from RTTI
    // - any TestName/TestMethod[] index higher or equal to this value has been
    // added by a specific call to the Add() method
    property InternalTestsCount: integer read fInternalTestsCount;
  published
    { all published methods of the children will be run as individual tests
      - these methods must be declared as procedure with no parameter }
  end;
{$M-}

  TSynTests = class;

  /// a class implementing a test case
  // - should handle a test unit, i.e. one or more tests
  // - individual tests are written in the published methods of this class
  TSynTestCase = class(TSynTest)
  protected
    fOwner: TSynTests;
    fAssertions: integer;
    fAssertionsFailed: integer;
    fAssertionsBeforeRun: integer;
    fAssertionsFailedBeforeRun: integer;
    fMethodIndex: integer;
    fTestCaseIndex: integer;
    /// any number not null assigned to this field will display a "../s" stat
    fRunConsoleOccurenceNumber: cardinal;
    /// any number not null assigned to this field will display a "using .. MB" stat
    fRunConsoleMemoryUsed: Int64;
    /// any text assigned to this field will be displayed on console
    fRunConsole: string;
    /// override this method to process some clean-up before Destroy call
    // - WARNING: this method should be re-entrant - so using FreeAndNil() is
    // a good idea in this method :)
    procedure CleanUp; virtual;
  public
    /// create the test case instance
    // - must supply a test suit owner
    // - if an identifier is not supplied, the class name is used, after
    // T[Syn][Test] left trim and un-camel-case
    constructor Create(Owner: TSynTests; const Ident: string = ''); virtual;
    /// clean up the instance
    // - will call CleanUp, even if already done before
    destructor Destroy; override;
    /// used by the published methods to run a test assertion
    // - condition must equals TRUE to pass the test
    procedure Check(condition: Boolean; const msg: string = '');
      {$ifdef HASINLINE}inline;{$endif}
    /// used by the published methods to run a test assertion
    // - condition must equals TRUE to pass the test
    // - function return TRUE if the condition failed, in order to allow the
    // caller to stop testing with such code:
    // ! if CheckFailed(A=10) then exit;
    function CheckFailed(condition: Boolean; const msg: string = ''): Boolean;
      {$ifdef HASINLINE}inline;{$endif}
    /// used by the published methods to run a test assertion
    // - condition must equals FALSE to pass the test
    // - function return TRUE if the condition failed, in order to allow the
    // caller to stop testing with such code:
    // ! if CheckNot(A<>10) then exit;
    function CheckNot(condition: Boolean; const msg: string = ''): Boolean;
      {$ifdef HASINLINE}inline;{$endif}
    /// used by the published methods to run a test assertion about two double values
    function CheckSame(const Value1,Value2: double;
      const Precision: double=1E-12; const msg: string = ''): Boolean;
    /// perform a string comparison with several value
    // - test passes if (Value=Values[0]) or (Value=Value[1]) or (Value=Values[...
    // and ExpectedResult=true 
    procedure CheckMatchAny(const Value: RawUTF8; const Values: array of RawUTF8;
      CaseSentitive: Boolean=true; ExpectedResult: Boolean=true; const msg: string = '');
    /// create a temporary string random content, WinAnsi (code page 1252) content 
    // - it somewhat faster if CharCount is a multiple of 5
    class function RandomString(CharCount: Integer): RawByteString;
    /// create a temporary UTF-8 string random content, using WinAnsi
    // (code page 1252) content
    // - it somewhat faster if CharCount is a multiple of 5
    class function RandomUTF8(CharCount: Integer): RawUTF8;
    /// create a temporary UTF-16 string random content, using WinAnsi
    // (code page 1252) content
    // - it somewhat faster if CharCount is a multiple of 5
    class function RandomUnicode(CharCount: Integer): SynUnicode;
    /// create a temporary string random content, using ASCII 7 bit content
    // - it somewhat faster if CharCount is a multiple of 5
    class function RandomAnsi7(CharCount: Integer): RawByteString;
    /// create a temporary string, containing some fake text, with paragraphs
    class function RandomTextParagraph(WordCount: Integer;
      LastPunctuation: AnsiChar='.'; const RandomInclude: RawUTF8=''): RawUTF8;
    /// this method is triggered internaly - e.g. by Check() - when a test failed
    procedure TestFailed(const msg: string);
    /// will add to the console a message with a speed estimation
    // - speed is computed from the method start
    procedure NotifyTestSpeed(const ItemName: string; ItemCount: integer;
      SizeInBytes: cardinal=0);
    /// the test suit which owns this test case
    property Owner: TSynTests read fOwner;
    /// the test name
    // - either the Ident parameter supplied to the Create() method, either
    // an uncameled text from the class name
    property Ident: string read GetIdent;
    /// the number of assertions (i.e. Check() method call) for this test case
    property Assertions: integer read fAssertions;
    /// the number of assertions (i.e. Check() method call) for this test case
    property AssertionsFailed: integer read fAssertionsFailed;
    /// the index of the associated Owner.TestMethod[] which created this test
    property MethodIndex: integer read fMethodIndex;
    /// the index of the test case, starting at 0 for the associated MethodIndex
    property TestCaseIndex: integer read fTestCaseIndex;
  published
    { all published methods of the children will be run as individual tests
      - these methods must be declared as procedure with no parameter }
  end;

  TSynTestCaseClass = class of TSynTestCase;

  /// a class used to run a suit of test cases
  TSynTests = class(TSynTest)
  protected
    /// any number not null assigned to this field will display a "../sec" stat
    fRunConsoleOccurenceNumber: cardinal;
    /// a list containing all failed tests after a call to the Run method
    // - if integer(Objects[]) is equal or higher than InternalTestsCount,
    // the Objects[] points to the TSynTestCase, and the Strings[] to the
    // associated failure message
    // - if integer(Objects[]) is lower than InternalTestsCount, then
    // it is an index to the corresponding published method, and the Strings[]
    // contains the associated failure message
    fFailed: TStringList;
    fTestCase: TObjectList;
    fAssertions: integer;
    fAssertionsFailed: integer;
    fCurrentMethod, fCurrentMethodIndex: integer;
    fSaveToFile: Text;
    function GetTestCase(Index: integer): TSynTestCase;
    function GetTestCaseCount: Integer;
    function GetFailedCaseIdent(Index: integer): string;
    function GetFailedCount: integer;
    function GetFailedMessage(Index: integer): string;
    function GetFailedCase(Index: integer): TSynTestCase;
    procedure CreateSaveToFile; virtual;
    procedure Color(aColor: TConsoleColor);
    /// called when a test case failed: default is to add item to fFailed[]
    procedure Failed(const msg: string; aTest: TSynTestCase); virtual;
    /// this method is called before every run
    // - default implementation will just return nil
    // - can be overridden to implement a per-test case logging for instance
    function BeforeRun(const TestName: RawUTF8): IUnknown; virtual;
    /// this method is called during the run, for every testcase
    // - this implementation just report some minimal data to the console
    // by default, but may be overridden to update a real UI or reporting system
    // - the TestMethodIndex is first -1 before any TestMethod[] method call,
    // then called once after every TestMethod[] run
    procedure DuringRun(TestCaseIndex, TestMethodIndex: integer); virtual;
  public
    /// you can put here some text to be displayed at the end of the messages
    // - some internal versions, e.g.
    // - every line of text must explicitly BEGIN with #13#10
    CustomVersions: string;
    /// contains the run elapsed time
    RunTimer, TestTimer, TotalTimer: TPrecisionTimer;
    /// create the test instance
    // - if an identifier is not supplied, the class name is used, after
    // T[Syn][Test] left trim and un-camel-case
    // - this constructor will add all published methods to the internal
    // test list, accessible via the Count/TestName/TestMethod properties
    constructor Create(const Ident: string = '');
    /// finalize the class instance
    // - release all registered Test case instance
    destructor Destroy; override;
    /// save the debug messages into an external file
    // - if no file name is specified, the current Ident is used
    procedure SaveToFile(const DestPath: TFileName; const FileName: TFileName='');
    /// register a specified Test case instance
    // - all these instances will be freed by the TSynTests.Destroy
    // - the published methods of the children must call this method in order
    // to add test cases
    // - example of use (code from a TSynTests published method):
    // !  AddCase(TOneTestCase.Create(self));
    procedure AddCase(TestCase: TSynTestCase); overload;
    /// register a specified Test case from its class name
    // - all these instances will be freed by the TSynTests.Destroy
    // - the published methods of the children must call this method in order
    // to add test cases
    // - example of use (code from a TSynTests published method):
    // !  AddCase(TOneTestCase);
    procedure AddCase(TestCase: TSynTestCaseClass); overload;
    /// register a specified Test case from its class name
    // - an instance of the supplied class is created, and will be freed by
    // TSynTests.Destroy
    // - the published methods of the children must call this method in order
    // to add test cases
    // - example of use (code from a TSynTests published method):
    // !  AddCase([TOneTestCase]);
    procedure AddCase(const TestCase: array of TSynTestCaseClass); overload;
    /// call of this method will run all associated tests cases
    // - function will return TRUE if all test passed
    // - all failed test cases will be added to the Failed[] list
    // - the TestCase[] list is created first, by running all published methods,
    // which must call the AddCase() method above to register test cases
    // - the Failed[] list is cleared at the beginning of the run
    // - Assertions and AssertionsFailed properties are reset and computed during
    // the run
    function Run: Boolean; virtual;
    /// the number of items in the TestCase[] array
    property TestCaseCount: Integer read GetTestCaseCount;
    /// an array containing all registered Test case instances
    // - Test cases are registered by the AddCase() method above, mainly
    // by published methods of the children
    // - Test cases instances are freed by TSynTests.Destroy
    property TestCase[Index: integer]: TSynTestCase read GetTestCase;
    /// number of failed tests after the last call to the Run method
    property FailedCount: integer read GetFailedCount;
    /// retrieve the TSynTestCase instance associated with this failure
    // - returns nil if this failure was not triggered by a TSynTestCase,
    // but directly by a method
    property FailedCase[Index: integer]: TSynTestCase read GetFailedCase;
    /// retrieve the ident of the case test associated with this failure
    property FailedCaseIdent[Index: integer]: string read GetFailedCaseIdent;
    /// retrieve the error message associated with this failure
    property FailedMessage[Index: integer]: string read GetFailedMessage;
    /// the number of assertions (i.e. Check() method call) in all tests
    // - this property is set by the Run method above
    property Assertions: integer read fAssertions;
    /// the number of assertions (i.e. Check() method call) which failed in all tests
    // - this property is set by the Run method above
    property AssertionsFailed: integer read fAssertionsFailed;
  published
    { all published methods of the children will be run as test cases registering
      - these methods must be declared as procedure with no parameter
      - every method should create a customized TSynTestCase instance,
        which will be registered with the AddCase() method, then automaticaly
        destroyed during the TSynTests destroy  }
  end;


/// convert a size to a human readable value
// - append MB, KB or B symbol
// - for MB and KB, add one fractional digit
function KB(bytes: Int64): RawUTF8;

/// convert a micro seconds elapsed time into a human readable value
................................................................................
function IntToThousandString(Value: integer; const ThousandSep: RawUTF8=','): RawUTF8;

/// return the Delphi Compiler Version
// - returns 'Delphi 2007' or 'Delphi 2010' e.g.
function GetDelphiCompilerVersion: RawUTF8;



{ ************ Logging classes and functions }

type
  /// a debugger symbol, as decoded by TSynMapFile from a .map file
  TSynMapSymbol = record
    /// symbol internal name
    Name: RawUTF8;
    /// starting offset of this symbol in the executable
    Start: cardinal;
    /// end offset of this symbol in the executable
    Stop: cardinal;
  end;
  PSynMapSymbol = ^TSynMapSymbol;
  /// a dynamic array of symbols, as decoded by TSynMapFile from a .map file
  TSynMapSymbolDynArray = array of TSynMapSymbol;

  /// a debugger unit, as decoded by TSynMapFile from a .map file
  TSynMapUnit = record
    /// Name, Start and Stop of this Unit
    Symbol: TSynMapSymbol;
    /// associated source file name
    FileName: RawUTF8;
    /// list of all mapped source code lines of this unit
    Line: TIntegerDynArray;
    /// start code address of each source code lin
    Addr: TIntegerDynArray;
  end;
  /// a dynamic array of units, as decoded by TSynMapFile from a .map file
  TSynMapUnitDynArray = array of TSynMapUnit;

  {$M+}
  /// retrieve a .map file content, to be used e.g. with TSynLog to provide
  // additional debugging information
  // - original .map content can be saved as .mab file in a more optimized format
  TSynMapFile = class
  protected
    fMapFile: TFileName;
    fSymbol: TSynMapSymbolDynArray;
    fUnit: TSynMapUnitDynArray;
    fSymbols: TDynArray;
    fUnits: TDynArrayHashed;
    fGetModuleHandle: PtrUInt;
    fHasDebugInfo: boolean;
  public
    /// get the available debugging information
    // - if aExeName is specified, will use it in its search for .map/.mab
    // - if aExeName is not specified, will use the currently running .exe/.dll
    // - it will first search for a .map matching the file name: if found,
    // will be read to retrieve all necessary debugging information - a .mab
    // file will be also created in the same directory (if MabCreate is TRUE)
    // - if .map is not not available, will search for the .mab file
    // - if no .mab is available, will search for a .mab appended to the .exe/.dll
    // - if nothing is available, will log as hexadecimal pointers, without
    // debugging information
    constructor Create(const aExeName: TFileName=''; MabCreate: boolean=true);
    /// save all debugging information in the .mab custom binary format
    // - if no file name is specified, it will be saved as ExeName.mab or DllName.mab
    // - this file content can be appended to the executable via SaveToExe method
    // - this function returns the created file name
    function SaveToFile(const aFileName: TFileName=''): TFileName;
    /// save all debugging informat in our custom binary format
    procedure SaveToStream(aStream: TStream);
    /// append all debugging information to an executable (or library)
    // - the executable name must be specified, because it's impossible to
    // write to the executable of a running process
    // - this method will work for .exe and for .dll (or .ocx)
    procedure SaveToExe(const aExeName: TFileName);
    /// add some debugging information according to the specified memory address
    // - will create a global TSynMapFile instance for the current process, if
    // necessary
    // - if no debugging information is available (.map or .mab), will write
    // the address as hexadecimal
    class procedure Log(W: TTextWriter; Addr: PtrUInt);
    /// retrieve a symbol according to an absolute code address
    function FindSymbol(aAddr: cardinal): integer;
    /// retrieve an unit and source line, according to an absolute code address
    function FindUnit(aAddr: cardinal; out LineNumber: integer): integer;
    /// return the symbol location according to the supplied absolute address
    // - i.e. unit name, symbol name and line number (if any), as plain text
    // - returns '' if no match found
    function FindLocation(aAddr: Cardinal): RawUTF8;
    /// all symbols associated to the executable
    property Symbols: TSynMapSymbolDynArray read fSymbol;
    /// all units, including line numbers, associated to the executable
    property Units: TSynMapUnitDynArray read fUnit;
  published
    /// the associated file name
    property FileName: TFileName read fMapFile;
    /// equals true if a .map or .mab debugging information has been loaded
    property HasDebugInfo: boolean read fHasDebugInfo;
  end;
  {$M-}

  {$M+} { we need the RTTI for the published methods of the logging classes }

  TSynLog = class;
  TSynLogClass = class of TSynLog;
  TSynLogFamily = class;
  TSynLogFile = class;
  
  {$M-}

  /// a generic interface used for logging a method
  // - you should create one TSynLog instance at the beginning of a block code
  // using TSynLog.Enter: the ISynLog will be released automaticaly by the
  // compiler at the end of the method block, marking it's executation end
  // - all logging expect UTF-8 encoded text, i.e. usualy English text
  ISynLog = interface(IUnknown)
    ['{527AC81F-BC41-4717-B089-3F74DE56F1AE}']
{$ifndef DELPHI5OROLDER}
    /// call this method to add some information to the log at a specified level
    // - see the format in TSynLog.Log() method description
    // (not compatible with default SysUtils.Format function)
    // - if Instance is set, it will log the corresponding class name and address
    // (to be used if you didn't call TSynLog.Enter() method first)
    // - note that cardinal values should be type-casted to Int64() (otherwise
    // the integer mapped value will be transmitted, therefore wrongly)
    procedure Log(Level: TSynLogInfo; TextFmt: PWinAnsiChar; const TextArgs: array of const;
      Instance: TObject=nil); overload;
{$endif}
    /// call this method to add some information to the log at a specified level
    // - if Instance is set and Text is not '', it will log the corresponding
    // class name and address (to be used e.g. if you didn't call TSynLog.Enter()
    // method first)
    // - if Instance is set and Text is '', will behave the same as
    // Log(Level,Instance), i.e. write the Instance as JSON content
    procedure Log(Level: TSynLogInfo; const Text: RawUTF8;
      Instance: TObject=nil; TextTruncateAtLength: integer=maxInt); overload;
    /// call this method to add the content of an object to the log at a
    // specified level
    // - TSynLog will write the class and hexa address - TSQLLog will write the
    // object JSON content
    procedure Log(Level: TSynLogInfo; Instance: TObject); overload;
    /// call this method to add the content of most low-level types to the log
    // at a specified level
    // - TSynLog will handle enumerations and dynamic array; TSQLLog will be
    // able to write TObject/TSQLRecord and sets content as JSON
    procedure Log(Level: TSynLogInfo; aName: PWinAnsiChar;
      aTypeInfo: pointer; var aValue; Instance: TObject=nil); overload;
    /// call this method to add the caller address to the log at the specified level
    // - if the debugging info is available from TSynMapFile, will log the
    // unit name, associated symbol and source code line
    procedure Log(Level: TSynLogInfo=sllTrace); overload;
    /// call this method to add some multi-line information to the log at a
    // specified level
    // - LinesToLog content will be added, one line per one line, delimited by
    // #13#10 (CRLF)
    // - if a line starts with IgnoreWhenStartWith (already uppercase), it won't
    // be added to the log content (to be used e.g. with '--' for SQL statements)
    procedure LogLines(Level: TSynLogInfo; LinesToLog: PUTF8Char;
      aInstance: TObject=nil; const IgnoreWhenStartWith: PAnsiChar=nil);
    /// retrieve the associated logging instance
    function Instance: TSynLog;
  end;

  /// this event can be set for a TSynLogFamily to archive any deprecated log
  // into a custom compressed format
  // - will be called by TSynLogFamily when TSynLogFamily.Destroy identify
  // some outdated files
  // - the aOldLogFileName will contain the .log file with full path
  // - the aDestinationPath parameter will contain 'ArchivePath\log\YYYYMM\'
  // - should return true on success, false on error
  // - example of matching event handler are EventArchiveDelete/EventArchiveSynLZ
  // or EventArchiveZip in SynZip.pas
  // - this event handler will be called one time per .log file to archive,
  // then one last time with aOldLogFileName='' in order to close any pending
  // archive (used e.g. by EventArchiveZip to open the .zip only once)
  TSynLogArchiveEvent = function(const aOldLogFileName, aDestinationPath: TFileName): boolean;

  /// this event can be set for a TSynLogFamily to customize the file rotation
  // - will be called by TSynLog.PerformRotation
  // - should return TRUE if the function did process the file name
  // - should return FALSE if the function did not do anything, so that the
  // caller should perform the rotation as usual  
  TSynLogRotateEvent = function(aLog: TSynLog; const aOldLogFileName: TFileName): boolean;

  /// how threading is handled by the TSynLogFamily
  // - by default, ptMergedInOneFile will indicate that all threads are logged
  // in the same file, in occurence order
  // - if set to ptOneFilePerThread, it will create one .log file per thread
  // - if set to ptIdentifiedInOnFile, a new column will be added for each
  // log row, with the corresponding ThreadID - LogView tool will be able to
  // display per-thread logging, if needed - note that your application shall
  // use a thread pool (just like all mORMot servers classes do), otherwise
  // some random hash collision may occur if Thread IDs are not recycled enough
  TSynLogPerThreadMode = (
    ptMergedInOneFile, ptOneFilePerThread, ptIdentifiedInOnFile);

  /// how stack trace shall be computed during logging
  TSynLogStackTraceUse = (stManualAndAPI,stOnlyAPI,stOnlyManual);

  /// how file existing shall be handled during logging
  TSynLogExistsAction = (acOverwrite, acAppend, acAppendWithHeader);

  {/ regroup several logs under an unique family name
   - you should usualy use one family per application or per architectural
     module: e.g. a server application may want to log in separate files the
     low-level Communication, the DB access, and the high-level process
   - initialize the family settings before using them, like in this code:
     ! with TSynLogDB.Family do begin
     !   Level := LOG_VERBOSE;
     !   PerThreadLog := ptOneFilePerThread;
     !   DestinationPath := 'C:\Logs';
     ! end;
   - then use the logging system inside a method:
     ! procedure TMyDB.MyMethod;
     ! var ILog: ISynLog;
     ! begin
     !   ILog := TSynLogDB.Enter(self,'MyMethod');
     !   // do some stuff
     !   ILog.Log(sllInfo,'method called');
     ! end; }
  TSynLogFamily = class
  protected
    fLevel, fLevelStackTrace: TSynLogInfos;
    fArchiveAfterDays: Integer;
    fArchivePath: TFileName;
    fOnArchive: TSynLogArchiveEvent;
    fOnRotate: TSynLogRotateEvent;
    fPerThreadLog: TSynLogPerThreadMode;
    fIncludeComputerNameInFileName: boolean;
    fCustomFileName: TFileName;
    fGlobalLog: TSynLog;
    fSynLogClass: TSynLogClass;
    fIdent: integer;
    fDestinationPath: TFileName;
    fDefaultExtension: TFileName;
    fBufferSize: integer;
    fHRTimeStamp: boolean;
    fWithUnitName: boolean;
    fNoFile: boolean;
    {$ifdef MSWINDOWS}
    fAutoFlush: cardinal;
    {$endif}
    {$ifndef NOEXCEPTIONINTERCEPT}
    fHandleExceptions: boolean;
    {$endif}
    fStackTraceLevel: byte;
    fStackTraceUse: TSynLogStackTraceUse;
    fFileExistsAction: TSynLogExistsAction;
    fExceptionIgnore: TList;
    fEchoToConsole: TSynLogInfos;
    fEchoCustom: TOnTextWriterEcho;
    fEchoRemoteClient: TObject;
    fEchoRemoteClientOwned: boolean;
    fEchoRemoteEvent: TOnTextWriterEcho;
    fEndOfLineCRLF: boolean;
    fDestroying: boolean;
    fRotateFileCurrent: cardinal;
    fRotateFileCount: cardinal;
    fRotateFileSize: cardinal;
    fRotateFileAtHour: integer;
    function CreateSynLog: TSynLog;
    {$ifdef MSWINDOWS}
    procedure SetAutoFlush(TimeOut: cardinal);
    {$endif}
    procedure SetDestinationPath(const value: TFileName);
    procedure SetLevel(aLevel: TSynLogInfos);
    procedure SetEchoToConsole(aEnabled: TSynLogInfos);
  public
    /// intialize for a TSynLog class family
    // - add it in the global SynLogFileFamily[] list
    constructor Create(aSynLog: TSynLogClass);
    /// release associated memory
    // - will archive older DestinationPath\*.log files, according to
    // ArchiveAfterDays value and ArchivePath
    destructor Destroy; override;

    /// retrieve the corresponding log file of this thread and family
    // - creates the TSynLog if not already existing for this current thread
    function SynLog: TSynLog;
    /// register one object and one echo callback for remote logging
    // - aClient is typically a mORMot's TSQLHttpClient
    // - if aClientOwnedByFamily is TRUE, its life time will be manage by this
    // TSynLogFamily: it will staty alive until this TSynLogFamily is destroyed,
    // or the EchoRemoteStop() method called
    // - aClientEvent should be able to send the log row to the remote server
    // - typical use may be:
    procedure EchoRemoteStart(aClient: TObject; const aClientEvent: TOnTextWriterEcho;
      aClientOwnedByFamily: boolean);
    /// stop echo remote logging
    // - will free the aClient instance supplied to EchoRemoteStart
    procedure EchoRemoteStop;

    /// you can add some exceptions to be ignored to this list
    // - for instance, EConvertError may be added to the list
    property ExceptionIgnore: TList read fExceptionIgnore;
    /// event called to archive the .log content after a defined delay
    // - Destroy will parse DestinationPath folder for *.log files matching
    // ArchiveAfterDays property value
    // - you can set this property to EventArchiveDelete in order to delete deprecated
    // files, or EventArchiveSynLZ to compress the .log file into our propertary
    // SynLZ format: resulting file name will be ArchivePath\log\YYYYMM\*.log.synlz
    // (use FileUnSynLZ function to uncompress it)
    // - if you use SynZip.EventArchiveZip, the log files will be archived in
    // ArchivePath\log\YYYYMM.zip
    // - the aDestinationPath parameter will contain 'ArchivePath\log\YYYYMM\'
    // - this event handler will be called one time per .log file to archive,
    // then one last time with aOldLogFileName='' in order to close any pending
    // archive (used e.g. by EventArchiveZip to open the .zip only once)
    property OnArchive: TSynLogArchiveEvent read fOnArchive write fOnArchive;
    /// event called to perform a custom file rotation
    // - will be checked by TSynLog.PerformRotation to customize the rotation
    // process and do not perform the default step, if the callback returns TRUE
    property OnRotate: TSynLogRotateEvent read fOnRotate write fOnRotate;
    /// if the some kind of events shall be echoed to the console
    // - note that it will slow down the logging process a lot (console output
    // is slow by nature under Windows, but may be convenient for interactive
    // debugging of services, for instance
    // - this property shall be set before any actual logging, otherwise it
    // will have no effect
    // - can be set e.g. to LOG_VERBOSE in order to echo every kind of events
    // - EchoCustom or EchoService can be activated separately
    property EchoToConsole: TSynLogInfos read fEchoToConsole write SetEchoToConsole;
    /// can be set to a callback which will be called for each log line
    // - could be used with a third-party logging system
    // - EchoToConsole or EchoService can be activated separately
    // - you may even disable the integrated file output, via NoFile := true
    property EchoCustom: TOnTextWriterEcho read fEchoCustom write fEchoCustom;
  published
    /// the associated TSynLog class
    property SynLogClass: TSynLogClass read fSynLogClass;
    /// index in global SynLogFileFamily[] and SynLogFileIndexThreadVar[] lists
    property Ident: integer read fIdent;
    /// the current level of logging information for this family
    // - can be set e.g. to LOG_VERBOSE in order to log every kind of events
    property Level: TSynLogInfos read fLevel write SetLevel;
    /// the levels which will include a stack trace of the caller
    // - by default, contains sllError, sllException, sllExceptionOS, sllFail,
    // sllLastError and sllStackTrace
    // - exceptions will always trace the stack
    property LevelStackTrace: TSynLogInfos read fLevelStackTrace write fLevelStackTrace;
    /// the folder where the log must be stored
    // - by default, is in the executable folder
    property DestinationPath: TFileName read fDestinationPath write SetDestinationPath;
    /// the file extension to be used
    // - is '.log' by default
    property DefaultExtension: TFileName read fDefaultExtension write fDefaultExtension;
    /// if TRUE, the log file name will contain the Computer name - as '(MyComputer)'
    property IncludeComputerNameInFileName: boolean read fIncludeComputerNameInFileName write fIncludeComputerNameInFileName;
    /// can be used to customized the default file name
    // - by default, the log file name is computed from the executable name
    // (and the computer name if IncludeComputerNameInFileName is true)
    // - you can specify your own file name here, to be used instead
    // - this file name should not contain any folder, nor file extension (which
    // are set by DestinationPath and DefaultExtension properties) 
    property CustomFileName: TFileName read fCustomFileName write fCustomFileName;
    /// the folder where old log files must be compressed
    // - by default, is in the executable folder, i.e. the same as DestinationPath
    // - the 'log\' sub folder name will always be appended to this value
    // - will then be used by OnArchive event handler to produce, with the
    // current file date year and month, the final path (e.g.
    // 'ArchivePath\Log\YYYYMM\*.log.synlz' or 'ArchivePath\Log\YYYYMM.zip')
    property ArchivePath: TFileName read fArchivePath write fArchivePath;
    /// number of days before OnArchive event will be called to compress
    // or delete deprecated files
    // - will be set by default to 7 days
    // - will be used by Destroy to call OnArchive event handler on time
    property ArchiveAfterDays: Integer read fArchiveAfterDays write fArchiveAfterDays;
    /// the internal in-memory buffer size, in bytes
    // - this is the number of bytes kept in memory before flushing to the hard
    // drive; you can call TSynLog.Flush method or set AutoFlushTimeOut to true
    // in order to force the writting to disk
    // - is set to 4096 by default (4 KB is the standard hard drive cluster size)
    property BufferSize: integer read fBufferSize write fBufferSize;
    /// define how thread will be identified during logging process
    // - by default, ptMergedInOneFile will indicate that all threads are logged
    // in the same file, in occurence order (so multi-thread process on server
    // side may be difficult to interpret)
    // - if RotateFileCount and RotateFileSizeKB/RotateFileDailyAtHour are set,
    // will be ignored (internal thread list shall be defined for one process)
    property PerThreadLog: TSynLogPerThreadMode read fPerThreadLog write fPerThreadLog;
    /// if TRUE, will log high-resolution time stamp instead of ISO 8601 date and time
    // - this is less human readable, but allows performance profiling of your
    // application on the customer side (using TSynLog.Enter methods)
    // - set to FALSE by default, or if RotateFileCount and RotateFileSizeKB /
    // RotateFileDailyAtHour are set (the high resolution frequency is set
    // in the log file header, so expects a single file) 
    property HighResolutionTimeStamp: boolean read fHRTimeStamp write fHRTimeStamp;
    /// if TRUE, will log the unit name with an object instance if available
    // - unit name is available from RTTI if the class has published properties
    // - set to FALSE by default
    property WithUnitName: boolean read fWithUnitName write fWithUnitName;
    {$ifdef MSWINDOWS}
    /// the time (in seconds) after which the log content must be written on
    // disk, whatever the current content size is
    // - by default, the log file will be written for every 4 KB of log - this
    // will ensure that the main application won't be slow down by logging
    // - in order not to loose any log, a background thread can be created
    // and will be responsible of flushing all pending log content every
    // period of time (e.g. every 10 seconds)
    property AutoFlushTimeOut: cardinal read fAutoFlush write SetAutoFlush;
    {$endif}
    /// force no log to be written to any file
    // - may be usefull in conjunction e.g. with EchoToConsole or any other
    // third-party logging component 
    property NoFile: boolean read fNoFile write fNoFile;
    /// auto-rotation of logging files
    // - set to 0 by default, meaning no rotation
    // - can be set to a number of rotating files: rotation and compression will 
    // happen, and main file size will be up to RotateFileSizeKB number of bytes,
    // or when RotateFileDailyAtHour time is reached
    // - if set to 1, no .synlz backup will be created, so the main log file will
    // be restarted from scratch when it reaches RotateFileSizeKB size or when
    // RotateFileDailyAtHour time is reached
    // - if set to a number > 1, some rotated files will be compressed using the
    // SynLZ algorithm, and will be named e.g. as MainLogFileName.0.synlz ..
    // MainLogFileName.7.synlz for RotateFileCount=9 (total count = 9, including
    // 1 main log file and 8 .synlz files)
    property RotateFileCount: cardinal read fRotateFileCount write fRotateFileCount;
    /// maximum size of auto-rotated logging files, in kilo-bytes (per 1024 bytes)
    // - specify the maximum file size upon which .synlz rotation takes place
    // - is not used if RotateFileCount is left to its default 0
    property RotateFileSizeKB: cardinal read fRotateFileSize write fRotateFileSize;
    /// fixed hour of the day where logging files rotation should be performed
    // - by default, equals -1, meaning no rotation
    // - you can set a time value between 0 and 23 to force the rotation at this
    // specified hour 
    // - is not used if RotateFileCount is left to its default 0
    property RotateFileDailyAtHour: integer read fRotateFileAtHour write fRotateFileAtHour;
    /// the recursive depth of stack trace symbol to write
    // - used only if exceptions are handled, or by sllStackTrace level
    // - default value is 20, maximum is 255
    // - if stOnlyAPI is defined as StackTraceUse under Windows XP, maximum 
    // value may be around 60, due to RtlCaptureStackBackTrace() API limitations
    property StackTraceLevel: byte read fStackTraceLevel write fStackTraceLevel;
    /// how the stack trace shall use only the Windows API
    // - the class will use low-level RtlCaptureStackBackTrace() API to retrieve
    // the call stack: in some cases, it is not able to retrieve it, therefore
    // a manual walk of the stack can be processed - since this manual call can
    // trigger some unexpected access violations or return wrong positions,
    // you can disable this optional manual walk by setting it to stOnlyAPI
    // - default is stManualAndAPI, i.e. use RtlCaptureStackBackTrace() API and
    // perform a manual stack walk if the API returned no address (or <3); but
    // within the IDE, it will use stOnlyAPI, to ensure no annoyning AV occurs
    property StackTraceUse: TSynLogStackTraceUse read fStackTraceUse write fStackTraceUse;
    /// /// how file existing shall be handled during logging
    property FileExistsAction: TSynLogExistsAction read fFileExistsAction write fFileExistsAction;
    /// define how the logger will emit its line feed
    // - by default (FALSE), a single CR (#13) char will be written, to save
    // storage space
    // - you can set this property to TRUE, so that CR+LF (#13#10) chars will
    // be appended instead
    // - TSynLogFile class and our LogView tool will handle both patterns
    property EndOfLineCRLF: boolean read fEndOfLineCRLF write fEndOfLineCRLF;
  end;

  /// thread-specific internal context used during logging
  // - this structure is a hashed-per-thread variable
  TSynLogThreadContext = record
    /// the corresponding Thread ID
    ID: cardinal;
    /// number of items stored in Recursion[]
    RecursionCount: integer;
    /// number of items available in Recursion[]
    // - faster than length(Recursion)
    RecursionCapacity: integer;
    /// used by TSynLog.Enter methods to handle recursivity calls tracing
    Recursion: array of record
      /// associated class instance to be displayed
      Instance: TObject;
      /// associated class type to be displayed
      ClassType: TClass;
      /// method name (or message) to be displayed
      MethodName: PUTF8Char;
      /// internal reference count used at this recursion level by TSynLog._AddRef
      RefCount: integer;
      /// the caller address, ready to display stack trace dump if needed
      Caller: PtrUInt;
      /// the time stamp at enter time
      EnterTimeStamp: Int64;
      /// if the method name is local, i.e. shall not be displayed at Leave()
      MethodNameLocal: (mnAlways, mnEnter, mnLeave);
    end;
  end;
  // pointer to thread-specific context information
  PSynLogThreadContext = ^TSynLogThreadContext;

  /// a per-family and/or per-thread log file content
  // - you should create a sub class per kind of log file
  // ! TSynLogDB = class(TSynLog);
  // - the TSynLog instance won't be allocated in heap, but will share a
  // per-thread (if Family.PerThreadLog=ptOneFilePerThread) or global private
  // log file instance
  // - was very optimized for speed, if no logging is written, and even during
  // log write (using an internal TTextWriter)
  // - can use available debugging information via the TSynMapFile class, for
  // stack trace logging for exceptions, sllStackTrace, and Enter/Leave labelling
  TSynLog = class(TObject, ISynLog)
  protected
    fFamily: TSynLogFamily;
    fWriter: TTextWriter;
    fWriterClass: TTextWriterClass;
    fWriterStream: TStream;
    fThreadLock: TRTLCriticalSection;
    fThreadContext: PSynLogThreadContext;
    fThreadID: cardinal;
    fThreadIndex: integer;
    fStartTimeStamp: Int64;
    fCurrentTimeStamp: Int64;
    {$ifdef MSWINDOWS}
    fFrequencyTimeStamp: Int64;
    {$endif}
    fFileName: TFileName;
    fFileRotationSize: cardinal;
    fFileRotationNextHour: Int64;
    fThreadHash: TWordDynArray;
    fThreadContexts: array of TSynLogThreadContext;
    fThreadContextCount: integer;
    fCurrentLevel: TSynLogInfo;
    fInternalFlags: set of (logHeaderWritten);
    {$ifdef FPC}
    function QueryInterface({$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} iid : tguid;out obj) : longint;{$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
    function _AddRef : longint;{$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
    function _Release : longint;{$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
    {$else}
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    {$endif}
    class function FamilyCreate: TSynLogFamily;
    procedure DoEnterLeave(aLevel: TSynLogInfo);
    procedure CreateLogWriter; virtual;
{$ifndef DELPHI5OROLDER}
    procedure LogInternal(Level: TSynLogInfo; TextFmt: PWinAnsiChar;
      const TextArgs: array of const; Instance: TObject); overload; 
{$endif}
    procedure LogInternal(Level: TSynLogInfo; const Text: RawUTF8;
      Instance: TObject; TextTruncateAtLength: integer); overload;
    procedure LogInternal(Level: TSynLogInfo; aName: PWinAnsiChar;
     aTypeInfo: pointer; var aValue; Instance: TObject=nil); overload; 
    // any call to this method MUST call UnLock
    function LogHeaderLock(Level: TSynLogInfo): boolean;
    procedure LogTrailerUnLock(Level: TSynLogInfo); {$ifdef HASINLINE}inline;{$endif}
    procedure LogCurrentTime;
    procedure LogFileHeader; virtual;
{$ifndef DELPHI5OROLDER}
    procedure AddMemoryStats; virtual;
{$endif}
    procedure AddErrorMessage(Error: cardinal);
    procedure AddStackTrace(Stack: PPtrUInt);
    procedure ComputeFileName; virtual;
    procedure PerformRotation; virtual;
    procedure AddRecursion(aIndex: integer; aLevel: TSynLogInfo);
    procedure LockAndGetThreadContext; {$ifdef HASINLINE}inline;{$endif}
    procedure LockAndGetThreadContextInternal(ID: DWORD);
    function Instance: TSynLog;
    function ConsoleEcho(Sender: TTextWriter; Level: TSynLogInfo;
      const Text: RawUTF8): boolean; virtual;
  public
    /// intialize for a TSynLog class instance
    // - WARNING: not to be called directly! Use Enter or Add class function instead
    constructor Create(aFamily: TSynLogFamily=nil);
    /// release all memory and internal handles
    destructor Destroy; override;
    /// flush all log content to file
    // - if ForceDiskWrite is TRUE, will wait until written on disk (slow)
    procedure Flush(ForceDiskWrite: boolean);
    /// flush all log content to file and close the file
    procedure CloseLogFile;
    /// flush all log content to file, close the file, and release the instance
    // - you should never call the Free method directly, since the instance
    // is registered in a global TObjectList and an access violation may
    // occur at application closing: you can use this Release method if you
    // are sure that you won't need this TSynLog instance any more
    // - ensure there is no pending Leave element in a stack-allocated ISynLog
    // (see below) 
    // - can be used e.g. to release the instance when finishing a thread when
    // Family.PerThreadLog=ptOneFilePerThread:
    // ! var
    // !   TThreadLogger : TSynLogClass = TSynLog;
    // !
    // ! procedure TMyThread.Execute;
    // ! var log : ISynLog;
    // ! begin
    // !   log := TThreadLogger.Enter(self);
    // ! ...
    // !   log := nil; // to force logging end of method
    // !   TThreadLogger.SynLog.Release;
    // ! end;
    procedure Release;
    {/ handle generic method enter / auto-leave tracing
     - this is the main method to be called within a procedure/function to trace:
     ! procedure TMyDB.SQLExecute(const SQL: RawUTF8);
     ! var ILog: ISynLog;
     ! begin
     !   ILog := TSynLogDB.Enter(self,'SQLExecute');
     !   // do some stuff
     !   ILog.Log(sllInfo,'SQL=%',[SQL]);
     ! end;
     - returning a ISynLog interface will allow you to have an automated
     sllLeave log created when the method is left (thanks to the hidden
     try..finally block generated by the compiler to protect the ISynLog var)
     - it is convenient to define a local variable to store the returned ISynLog
     and use it for any specific logging within the method execution 
     - if you just need to access the log inside the method block, you may
     not need any ISynLog interface variable:
     ! procedure TMyDB.SQLFlush;
     ! begin
     !   TSynLogDB.Enter(self,'SQLFlush');
     !   // do some stuff
     ! end;
     - if no Method name is supplied, it will use the caller address, and
     will write it as hexa and with full unit and symbol name, if the debugging
     information is available (i.e. if TSynMapFile retrieved the .map content):
     ! procedure TMyDB.SQLFlush;
     ! begin
     !   TSynLogDB.Enter(self);
     !   // do some stuff
     ! end;
     - note that supplying a method name is faster than using the .map content:
     if you want accurate profiling, it's better to use a method name or not to
     use a .map file - note that this method name shall be a constant, and not
     a locally computed variable, since it may trigger some random GPF at
     runtime - if it is a local variable, you can set aMethodNameLocal=true
     - if TSynLogFamily.HighResolutionTimeStamp is TRUE, high-resolution
     time stamp will be written instead of ISO 8601 date and time: this will
     allow performance profiling of the application on the customer side
     - Enter() will write the class name (and the unit name for classes with
     published properties, if TSynLogFamily.WithUnitName is true) for both
     enter (+) and leave (-) events:
      $ 20110325 19325801  +    MyDBUnit.TMyDB(004E11F4).SQLExecute
      $ 20110325 19325801 info   SQL=SELECT * FROM Table;
      $ 20110325 19325801  -    MyDBUnit.TMyDB(004E11F4).SQLExecute }
    class function Enter(aInstance: TObject=nil; aMethodName: PUTF8Char=nil;
      aMethodNameLocal: boolean=false): ISynLog; overload;
    /// retrieve the current instance of this TSynLog class
    // - to be used for direct logging, without any Enter/Leave:
    // ! TSynLogDB.Add.Log(llError,'The % statement didn't work',[SQL]);
    // - to be used for direct logging, without any Enter/Leave (one parameter
    // version - just the same as previous):
    // ! TSynLogDB.Add.Log(llError,'The % statement didn't work',SQL);
    // - is just a wrapper around Family.SynLog - the same code will work:
    // ! TSynLogDB.Family.SynLog.Log(llError,'The % statement didn't work',[SQL]);
    class function Add: TSynLog;
      {$ifdef HASINLINE}inline;{$endif}
    /// retrieve the family of this TSynLog class type
    class function Family: TSynLogFamily; overload;
    /// returns a logging class which will never log anything
    // - i.e. a TSynLog sub-class with Family.Level := []
    class function Void: TSynLogClass;
{$ifndef DELPHI5OROLDER}
    /// low-level method helper which can be called to make debugging easier
    // - log some warning message to the TSynLog family
    // - will force a manual breakpoint if tests are run from the IDE
    class procedure DebuggerNotify(const Args: array of const; Format: PWinAnsiChar=nil);
    /// call this method to add some information to the log at the specified level
    // - % = #37 indicates a string, integer, floating-point, or class parameter
    // to be appended as text (e.g. class name)
    // - $ = #36 indicates an integer to be written with 2 digits and a comma
    // - £ = #163 indicates an integer to be written with 4 digits and a comma
    // - µ = #181 indicates an integer to be written with 3 digits without any comma
    // - ¤ = #164 indicates CR+LF chars
    // - CR = #13 indicates CR+LF chars
    // - § = #167 indicates to trim last comma
    // - since some of this characters above are > #127, they are not UTF-8
    // ready, so we expect the input format to be WinAnsi, i.e. mostly English
    // text (with chars < #128) with some values to be inserted inside
    // - note that cardinal values should be type-casted to Int64() (otherwise
    // the integer mapped value will be transmitted, therefore wrongly)
    procedure Log(Level: TSynLogInfo; TextFmt: PWinAnsiChar; const TextArgs: array of const;
      aInstance: TObject=nil); overload;
    /// same as Log(Level,TextFmt,[]) but with one RawUTF8 parameter
    procedure Log(Level: TSynLogInfo; TextFmt: PWinAnsiChar; const TextArg: RawUTF8;
      aInstance: TObject=nil); overload;
    /// same as Log(Level,TextFmt,[]) but with one Int64 parameter
    procedure Log(Level: TSynLogInfo; TextFmt: PWinAnsiChar; const TextArg: Int64;
      aInstance: TObject=nil); overload;
{$endif}
    /// call this method to add some information to the log at the specified level
    // - if Instance is set and Text is not '', it will log the corresponding
    // class name and address (to be used e.g. if you didn't call TSynLog.Enter()
    // method first) - for instance
    // ! TSQLLog.Add.Log(sllDebug,'GarbageCollector',GarbageCollector);
    // will append this line to the log:
    // $ 0000000000002DB9 debug TObjectList(00425E68) GarbageCollector
    // - if Instance is set and Text is '', will behave the same as
    // Log(Level,Instance), i.e. write the Instance as JSON content
    procedure Log(Level: TSynLogInfo; const Text: RawUTF8;
      aInstance: TObject=nil; TextTruncateAtLength: integer=maxInt); overload;
    /// call this method to add the content of an object to the log at a
    // specified level
    // - this default implementation will just write the class name and its hexa
    // pointer value, and handle TList, TCollections and TStrings - for instance:
    // ! TSynLog.Add.Log(sllDebug,GarbageCollector);
    // will append this line to the log:
    // $ 20110330 10010005 debug {"TObjectList(00B1AD60)":["TObjectList(00B1AE20)","TObjectList(00B1AE80)"]}
    // - if aInstance is an Exception, it will handle its class name and Message:
    // $ 20110330 10010005 debug "EClassName(00C2129A)":"Exception message"
    // - use TSQLLog from mORMot.pas unit to add the record content, written
    // as human readable JSON
    procedure Log(Level: TSynLogInfo; aInstance: TObject); overload;
    /// call this method to add the content of most low-level types to the log
    // at a specified level
    // - this overridden implementation will write the value content,
    // written as human readable JSON: handle dynamic arrays and enumerations
    // - TSQLLog from mORMot.pas unit will be able to write
    // TObject/TSQLRecord and sets content as JSON
    procedure Log(Level: TSynLogInfo; aName: PWinAnsiChar;
      aTypeInfo: pointer; var aValue; Instance: TObject=nil); overload;
    /// call this method to add the caller address to the log at the specified level
    // - if the debugging info is available from TSynMapFile, will log the
    // unit name, associated symbol and source code line
    procedure Log(Level: TSynLogInfo); overload;
    /// call this method to add some multi-line information to the log at a
    // specified level
    // - LinesToLog content will be added, one line per one line, delimited by
    // #13#10 (CRLF)
    // - if a line starts with IgnoreWhenStartWith (already uppercase), it won't
    // be added to the log content (to be used e.g. with '--' for SQL statements)
    procedure LogLines(Level: TSynLogInfo; LinesToLog: PUTF8Char; aInstance: TObject=nil;
      const IgnoreWhenStartWith: PAnsiChar=nil);
  published
    /// the associated logging family
    property GenericFamily: TSynLogFamily read fFamily;
    /// the associated file name containing the log
    // - this is accurate only with the default implementation of the class:
    // any child may override it with a custom logging mechanism
    property FileName: TFileName read fFileName;
  end;

  /// this overridden class will create a .log file in case of a test case failure
  // - inherits from TSynTestsLogged instead of TSynTests in order to add
  // logging to your test suite (via a dedicated TSynLogTest instance)
  TSynTestsLogged = class(TSynTests)
  protected
    fLogFile: TSynLog;
    fConsoleDup: TTextWriter;
    procedure CreateSaveToFile; override;
    /// called when a test case failed: log into the file
    procedure Failed(const msg: string; aTest: TSynTestCase); override;
    /// this method is called before every run
    // - overridden implementation to implement a per-test case logging 
    function BeforeRun(const TestName: RawUTF8): IUnknown; override;
  public
    /// create the test instance and initialize associated LogFile instance
    // - this will allow logging of all exceptions to the LogFile
    constructor Create(const Ident: string = '');
    /// release associated memory
    destructor Destroy; override;
    /// the .log file generator created if any test case failed
    property LogFile: TSynLog read fLogFile;
  end;

  /// used by TSynLogFile to refer to a method profiling in a .log file
  // - i.e. map a sllEnter/sllLeave event in the .log file
  TSynLogFileProc = record
    /// the index of the sllEnter event in the TSynLogFile.fLevels[] array
    Index: cardinal;
    /// the associated time elapsed in this method (in micro seconds)
    // - computed from the sllLeave time difference (high resolution timer)
    Time: cardinal;
    /// the time elapsed in this method and not in nested methods
    // - computed from Time property, minus the nested calls
    ProperTime: cardinal;
  end;

  /// used by TSynLogFile to refer to global method profiling in a .log file
  // - i.e. map all sllEnter/sllLeave event in the .log file
  TSynLogFileProcDynArray = array of TSynLogFileProc;

  TSynLogFileProcArray = array[0..(MaxInt div sizeof(TSynLogFileProc))-1] of TSynLogFileProc;
  PSynLogFileProcArray = ^TSynLogFileProcArray;

  /// used by TSynLogFile.LogProcSort method
  TLogProcSortOrder = (
    soNone, soByName, soByOccurrence, soByTime, soByProperTime);
    
  /// used to parse a .log file, as created by TSynLog, into high-level data
  // - this particular TMemoryMapText class will retrieve only valid event lines
  // (i.e. will fill EventLevel[] for each line <> sllNone)
  // - Count is not the global text line numbers, but the number of valid events
  // within the file (LinePointers/Line/Strings will contain only event lines) -
  // it will not be a concern, since the .log header is parsed explicitely
  TSynLogFile = class(TMemoryMapText)
  protected
    /// map the events occurring in the .log file content
    fLevels: TSynLogInfoDynArray;
    fThreads: TWordDynArray;
    fThreadsRows: TCardinalDynArray;
    fThreadsCount: integer;
    fThreadsRowsCount: cardinal;
    fThreadMax: cardinal;
    fLineLevelOffset: cardinal;
    fLineTextOffset: cardinal;
    fLineHeaderCountToIgnore: integer;
    /// as extracted from the .log header
    fExeName, fExeVersion, fHost, fUser, fCPU, fOSDetailed, fInstanceName: RawUTF8;
    fExeDate: TDateTime;
{$ifdef MSWINDOWS}
    fOS: TWindowsVersion;
    fOSServicePack: integer;
    fWow64: boolean;
{$endif}
    fStartDateTime: TDateTime;
    /// retrieve all used event levels
    fLevelUsed: TSynLogInfos;
    /// =0 if date time resolution, >0 if high-resolution time stamp
    fFreq: Int64;
    /// used by EventDateTime() to compute date from time stamp
    fFreqPerDay: double;
    /// custom headers, to be searched as .ini content
    fHeaderLinesCount: integer;
    fHeaders: RawUTF8;
    /// method profiling data
    fLogProcCurrent: PSynLogFileProcArray;
    fLogProcCurrentCount: integer;
    fLogProcNatural: TSynLogFileProcDynArray;
    fLogProcNaturalCount: integer;
    fLogProcMerged: TSynLogFileProcDynArray;
    fLogProcMergedCount: integer;
    fLogProcIsMerged: boolean;
    fLogProcStack: array of cardinal;
    fLogProcStackCount: integer;
    fLogProcSortInternalOrder: TLogProcSortOrder;
    /// used by ProcessOneLine
    fLogLevelsTextMap: array[TSynLogInfo] of cardinal;
    procedure SetLogProcMerged(const Value: boolean);
    function GetEventText(index: integer): RawUTF8;
    /// retrieve headers + fLevels[] + fLogProcNatural[], and delete invalid fLines[]
    procedure LoadFromMap(AverageLineLength: integer=32); override;
    /// compute fLevels[] + fLogProcNatural[] for each .log line during initial reading
    procedure ProcessOneLine(LineBeg, LineEnd: PUTF8Char); override;
    /// called by LogProcSort method
    function LogProcSortComp(A,B: Integer): integer;
    procedure LogProcSortInternal(L,R: integer);
  public
    /// initialize internal structure
    constructor Create; override;
    /// returns TRUE if the supplied text is contained in the corresponding line
    function LineContains(const aUpperSearch: RawUTF8; aIndex: Integer): Boolean; override;
    /// retrieve the date and time of an event
    // - returns 0 in case of an invalid supplied index
    function EventDateTime(aIndex: integer): TDateTime;
    /// sort the LogProc[] array according to the supplied order
    procedure LogProcSort(Order: TLogProcSortOrder);
    /// return the number of matching events in the log
    function EventCount(const aSet: TSynLogInfos): integer;
    /// retrieve the level of an event
    // - is calculated by Create() constructor
    // - EventLevel[] array index is from 0 to Count-1
    property EventLevel: TSynLogInfoDynArray read fLevels;
    /// retrieve all used event levels
    // - is calculated by Create() constructor
    property EventLevelUsed: TSynLogInfos read fLevelUsed;
    /// retrieve the description text of an event
    // - returns '' if supplied index is out of range
    property EventText[index: integer]: RawUTF8 read GetEventText;
    /// retrieve all event thread IDs
    // - contains something if TSynLogFamily.PerThreadLog was ptIdentifiedInOnFile
    // - for ptMergedInOneFile (default) or ptOneFilePerThread logging process,
    // the array will be void (EventThread=nil)   
    property EventThread: TWordDynArray read fThreads;
    /// the number of threads 
    property ThreadsCount: cardinal read fThreadMax;
    /// the number of occurences of each thread ID
    property ThreadsRows: TCardinalDynArray read fThreadsRows;
    /// profiled methods information
    // - is calculated by Create() constructor
    // - will contain the sllEnter index, with the associated elapsed time
    // - number of items in the array is retrieved by the LogProcCount property
    property LogProc: PSynLogFileProcArray read fLogProcCurrent;
    /// the current sort order
    property LogProcOrder: TLogProcSortOrder read fLogProcSortInternalOrder;
    /// if the method information must be merged for the same method name
    property LogProcMerged: boolean read fLogProcIsMerged write SetLogProcMerged;
    /// all used event levels, as retrieved at log file content parsing
    property LevelUsed: TSynLogInfos read fLevelUsed;
    /// high-resolution time stamp frequence, as retrieved from log file header 
    // - equals 0 if date time resolution, >0 if high-resolution time stamp
    property Freq: Int64 read fFreq;
    /// custom headers, to be searched as .ini content
    property Headers: RawUTF8 read fHeaders;
  published
    /// the associated executable name (with path)
    // - returns e.g. 'C:\Dev\lib\SQLite3\exe\TestSQL3.exe'
    property ExecutableName: RawUTF8 read fExeName;
    /// the associated executable version
    // - returns e.g. '0.0.0.0'
    property ExecutableVersion: RawUTF8 read fExeVersion;
    /// the associated executable build date and time
    property ExecutableDate: TDateTime read fExeDate;
    /// for a library, the associated instance name (with path)
    // - returns e.g. 'C:\Dev\lib\SQLite3\exe\TestLibrary.dll'
    // - for an executable, will be left void
    property InstanceName: RawUTF8 read fInstanceName;
    /// the computer host name in which the process was running on
    property ComputerHost: RawUTF8 read fHost;
    /// the computer user name who launched the process
    property RunningUser: RawUTF8 read fUser;
    /// the computer CPU in which the process was running on
    // - returns e.g. '1*0-15-1027'
    property CPU: RawUTF8 read fCPU;
    {$ifdef MSWINDOWS}
    /// the computer Operating System in which the process was running on
    property OS: TWindowsVersion read fOS;
    /// the Operating System Service Pack number
    property ServicePack: integer read fOSServicePack;
    /// if the 32 bit process was running under WOW 64 virtual emulation
    property Wow64: boolean read fWow64;
    {$endif MSWINDOWS}
    /// the computer Operating System in which the process was running on
    // - returns e.g. '2.3=5.1.2600' for Windows XP
    property DetailedOS: RawUTF8 read fOSDetailed;
    /// the date and time at which the log file was started
    property StartDateTime: TDateTime read fStartDateTime;
    /// number of profiled methods in this .log file
    // - i.e. number of items in the LogProc[] array
    property LogProcCount: integer read fLogProcCurrentCount;
  end;

const
  /// up to 16 TSynLogFamily, i.e. TSynLog children classes can be defined
  MAX_SYNLOGFAMILY = 15;

  /// can be set to TSynLogFamily.Level in order to log all available events
  LOG_VERBOSE: TSynLogInfos = [succ(sllNone)..high(TSynLogInfo)];

  /// contains the logging levels for which stack trace should be dumped
  // - which are mainly exceptions or application errors
  LOG_STACKTRACE: TSynLogInfos = [sllLastError,sllError,sllException,sllExceptionOS];

  /// the text equivalency of each logging level, as written in the log file
  // - PCardinal(@LOG_LEVEL_TEXT[L][3])^ will be used for fast level matching
  // so text must be unique for characters [3..6] -> e.g. 'UST4'
  LOG_LEVEL_TEXT: array[TSynLogInfo] of string[7] = (
    '       ', ' info  ', ' debug ', ' trace ', ' warn  ', ' ERROR ',
    '  +    ', '  -    ',
    ' OSERR ', ' EXC   ', ' EXCOS ', ' mem   ', ' stack ', ' fail  ',
    ' SQL   ', ' cache ', ' res   ', ' DB    ', ' http  ', ' clnt  ', ' srvr  ',
    ' call  ', ' ret   ', ' auth  ',
    ' cust1 ', ' cust2 ', ' cust3 ', ' cust4 ', ' rotat ');

  /// the "magic" number used to identify .log.synlz compressed files, as
  // created by TSynLogFamily.EventArchiveSynLZ
  LOG_MAGIC = $ABA51051;

type
  /// calling context of TSynLogExceptionToStr callbacks
  TSynLogExceptionContext = record
    /// the raised exception class
    EClass: ExceptClass;
    /// the Delphi Exception instance
    // - may be nil for external/OS exceptions
    EInstance: Exception;
    /// the OS-level exception code
    // - could be $0EEDFAE0 of $0EEDFADE for Delphi-generated exceptions
    ECode: DWord;
    /// the address where the exception occured
    EAddr: PtrUInt;
    /// the current logging level
    // - may be either sllException or sllExceptionOS
    Level: TSynLogInfo;
  end;

  /// global hook callback to customize exceptions logged by TSynLog
  // - should return FALSE if Context.EAddr and Stack trace is to be appended
  TSynLogExceptionToStr = function(WR: TTextWriter; const Context: TSynLogExceptionContext): boolean;

  /// generic parent class of all custom Exception types of this unit
  ESynException = class(Exception)
  public
    /// constructor which will use FormatUTF8() instead of Format()
    // - expect % as delimitor, so is less error prone than %s %d %g
    // - will handle vtPointer/vtClass/vtObject/vtVariant kind of arguments,
    // appending class name for any class or object, the hexa value for a
    // pointer, or the JSON representation of the supplied variant
    constructor CreateUTF8(Format: PUTF8Char; const Args: array of const);
    {$ifndef NOEXCEPTIONINTERCEPT}
    /// can be used to customize how the exception is logged
    // - this default implementation will call the DefaultSynLogExceptionToStr()
    // function or the TSynLogExceptionToStrCustom global callback, if defined
    // - override this method to provide a custom logging content
    // - should return TRUE if Context.EAddr and Stack trace is not to be
    // written (i.e. as for any TSynLogExceptionToStr callback)
    function CustomLog(WR: TTextWriter; const Context: TSynLogExceptionContext): boolean; virtual;
    {$endif}
  end;

  /// exception raised by all TSynTable related code
  ETableDataException = class(ESynException);

  /// exception class associated to TDocVariant JSON/BSON document
  EDocVariant = class(ESynException);

  
var
  /// the kind of .log file generated by TSynTestsLogged
  TSynLogTestLog: TSynLogClass = TSynLog;

  /// allow to customize the Exception logging message
  TSynLogExceptionToStrCustom: TSynLogExceptionToStr = nil;


{$ifndef NOEXCEPTIONINTERCEPT}

/// default exception logging callback
// - will add the default Exception details, including any Exception.Message
// - if the exception inherits from ESynException
// - returns TRUE: caller will then append ' at EAddr' and the stack trace
function DefaultSynLogExceptionToStr(WR: TTextWriter; const Context: TSynLogExceptionContext): boolean;

{$endif}

/// compress a data content using the SynLZ algorithm from one stream into another
// - returns the number of bytes written to Dest
// - you should specify a Magic number to be used to identify the block
function StreamSynLZ(Source: TCustomMemoryStream; Dest: TStream; Magic: cardinal): integer; overload;

/// uncompress using the SynLZ algorithm from one stream into another
// - returns a newly create memory stream containing the uncompressed data
................................................................................
// - you should specify a Magic number to be used to identify the block
// - this function will also recognize the block at the end of the source stream
// (if was appended to an existing data - e.g. a .mab at the end of a .exe)
// - on success, Source will point after all read data (so that you can e.g.
// append several data blocks to the same stream)
function StreamUnSynLZ(Source: TStream; Magic: cardinal): TMemoryStream; overload;





/// uncompress using the SynLZ algorithm from one file into another
// - returns a newly create memory stream containing the uncompressed data
// - returns nil if source file is invalid (e.g. invalid name or invalid content)
// - you should specify a Magic number to be used to identify the block
// - this function will also recognize the block at the end of the source file
// (if was appended to an existing data - e.g. a .mab at the end of a .exe)
function StreamUnSynLZ(const Source: TFileName; Magic: cardinal): TMemoryStream; overload;
................................................................................

/// uncompress a memory bufer using the SynLZ algorithm and crc32c hashing
function SynLZDecompress(const Data: RawByteString): RawByteString; overload;

/// uncompress a memory bufer using the SynLZ algorithm and crc32c hashing
procedure SynLZDecompress(P: PAnsiChar; PLen: integer;out Result: RawByteString); overload;

/// a TSynLogArchiveEvent handler which will delete older .log files
function EventArchiveDelete(const aOldLogFileName, aDestinationPath: TFileName): boolean;

/// a TSynLogArchiveEvent handler which will compress older .log files
// using our proprietary SynLZ format
// - resulting file will have the .synlz extension and will be located
// in the aDestinationPath directory, i.e. TSynLogFamily.ArchivePath+'\log\YYYYMM\'
// - use UnSynLZ.dpr tool to uncompress it into .log textual file
// - SynLZ is much faster than zip for compression content, but proprietary
function EventArchiveSynLZ(const aOldLogFileName, aDestinationPath: TFileName): boolean;


resourcestring
  sInvalidIPAddress = '"%s" is an invalid IP v4 address';
  sInvalidEmailAddress = '"%s" is an invalid email address';
  sInvalidPattern = '"%s" does not match the expected pattern';
  sCharacter01n = 'character,character,characters';
  sInvalidTextLengthMin = 'Expect at least %d %s';
................................................................................

implementation

{$ifdef FPC}
uses
  SynFPCTypInfo // small wrapper unit around FPC's TypInfo.pp
  {$ifdef Linux}
  , SynFPCLinux,BaseUnix, Unix, dynlibs, crt
  {$endif} ;
{$endif}


{ ************ some fast UTF-8 / Unicode / Ansi conversion routines }

var
................................................................................
    {$else}
    raise ESynException.CreateUTF8('%.AnsiBufferToUnicode() not supported yet for CP=%',
      [self,CodePage]);
    {$endif FPC}
    {$endif MSWINDOWS}
    {$endif ISDELPHIXE}
    {$ifndef DELPHI5OROLDER}
    if result=Dest then
      TSynLogTestLog.DebuggerNotify([GetLastError,CodePage],'Error % on CodePage %');
    {$endif}
  end;
  result^ := #0;
end;

function TSynAnsiConvert.AnsiBufferToUTF8(Dest: PUTF8Char;
  Source: PAnsiChar; SourceChars: Cardinal): PUTF8Char;
................................................................................
  if SupportSSE42 then
    crc32c := @crc32csse42 else
{$endif PUREPASCAL}
    crc32c := @crc32cfast;
  DefaultHasher := crc32c;
end;

var
  StdOut: THandle;

{$ifdef MSWINDOWS}
const
  // lpMinimumApplicationAddress retrieved from Windows is very low $10000
  // - i.e. maximum number of ID per table would be 65536 in TSQLRecord.GetID
  // - so we'll force an higher and almost "safe" value as 1,048,576
  // (real value from runnning Windows is greater than $400000)
  MIN_PTR_VALUE = $100000;
................................................................................
  // see http://msdn.microsoft.com/en-us/library/ms724833(v=vs.85).aspx
  VER_NT_WORKSTATION = 1;
  VER_NT_DOMAIN_CONTROLLER = 2;
  VER_NT_SERVER = 3;
  SM_SERVERR2 = 89;
  PROCESSOR_ARCHITECTURE_AMD64 = 9;

var
  TextAttr: integer = ord(ccDarkGray);

{$ifndef UNICODE}
function GetVersionEx(var lpVersionInformation: TOSVersionInfoEx): BOOL; stdcall;
  external kernel32 name 'GetVersionExA';
{$endif}

function GetSystemTimeMillisecondsForXP: Int64; stdcall;
var fileTime: TFileTime;
................................................................................
   result := (result shl 32)+fileTime.dwLowDateTime;
   result := result div 10000;
   {$else}
   result := trunc(PInt64(@fileTime)^/10000); // 100 ns unit
   {$endif}
end;

procedure InitConsole;
begin
  if StdOut=0 then begin
   StdOut := GetStdHandle(STD_OUTPUT_HANDLE);
   if StdOut=INVALID_HANDLE_VALUE then
     StdOut := 0;
  end;
end;

procedure RetrieveSystemInfo;
var
  IsWow64Process: function(Handle: THandle; var Res: BOOL): BOOL; stdcall;
  GetNativeSystemInfo: procedure(var SystemInfo: TSystemInfo); stdcall;
  Res: BOOL;
  Kernel: THandle;
  P: pointer;
  Vers: TWindowsVersion;
begin
  Kernel := GetModuleHandle(kernel32);
  GetTickCount64 := GetProcAddress(Kernel,'GetTickCount64');
  if not Assigned(GetTickCount64) then
    GetTickCount64 := @GetSystemTimeMillisecondsForXP;
  InitConsole;
  IsWow64Process := GetProcAddress(Kernel,'IsWow64Process');
  Res := false;
  IsWow64 := Assigned(IsWow64Process) and
    IsWow64Process(GetCurrentProcess,Res) and Res;
  fillchar(SystemInfo,sizeof(SystemInfo),0);
  if IsWow64 then // see http://msdn.microsoft.com/en-us/library/ms724381(v=VS.85).aspx
    GetNativeSystemInfo := GetProcAddress(Kernel,'GetNativeSystemInfo') else
................................................................................
var i: Integer;
begin
  SetLength(Result,Source.Count);
  for i := 0 to Source.Count-1 do
    StringToUTF8(Source[i],Result[i]);
end;

const
  ANSICHARNOT01310: set of AnsiChar = [#1..#9,#11,#12,#14..#255];

/// find the position of the SEARCH] section in source
// - return true if SEARCH] was found, and store line after it in source
function FindSectionFirstLine(var source: PUTF8Char; search: PAnsiChar): boolean;
{$ifdef PUREPASCAL}
begin
  result := false;
  if source=nil then
................................................................................
      if VerQueryValue(Pt, '\', pointer(Info), Size2) then
        result := Info^.dwFileVersionMS;
    finally
      Freemem(Pt);
    end;
  end;
end;
{$endif}

function WndProcMethod(Hwnd: HWND; Msg,wParam,lParam: integer): integer; stdcall;
var obj: TObject;
    dsp: TMessage;
begin
  {$ifdef CPU64}
  obj := pointer(GetWindowLongPtr(HWnd,GWLP_USERDATA));
  {$else}
  obj := pointer(GetWindowLong(HWnd,GWL_USERDATA)); // faster than GetProp()
  {$endif}
  if not Assigned(obj) then
    result := DefWindowProc(HWnd,Msg,wParam,lParam) else begin
    dsp.msg := Msg;
    dsp.wParam := WParam;
    dsp.lParam := lParam;
    dsp.result := 0;
    obj.Dispatch(dsp);
................................................................................
    exit; // impossible to create window -> fail
  {$ifdef CPU64}
  SetWindowLongPtr(result,GWLP_USERDATA,PtrInt(aObject));
  SetWindowLongPtr(result,GWLP_WNDPROC,PtrInt(@WndProcMethod));
  {$else}
  SetWindowLong(result,GWL_USERDATA,PtrInt(aObject)); // faster than SetProp()
  SetWindowLong(result,GWL_WNDPROC,PtrInt(@WndProcMethod));
  {$endif}
end;

function ReleaseInternalWindow(var aWindowName: string; var aWindow: HWND): boolean;
begin
  if (aWindow<>0) and (aWindowName<>'') then begin
    {$ifdef CPU64}
    SetWindowLongPtr(aWindow,GWLP_WNDPROC,PtrInt(@DefWindowProc));
    {$else}
    SetWindowLong(aWindow,GWL_WNDPROC,PtrInt(@DefWindowProc));
    {$endif}
    DestroyWindow(aWindow);
    Windows.UnregisterClass(pointer(aWindowName),hInstance);
    aWindow := 0;
    aWindowName := '';
    result := true;
  end else
    result := false;
end;

procedure TextColor(Color: TConsoleColor);



var oldAttr: integer;
begin
  InitConsole;
  oldAttr := TextAttr;
  TextAttr := (TextAttr and $F0) or ord(Color);
  if TextAttr<>oldAttr then
    SetConsoleTextAttribute(StdOut,TextAttr);
end;


procedure TextBackground(Color: TConsoleColor);
var oldAttr: integer;
begin
  InitConsole;
  oldAttr := TextAttr;
  TextAttr := (TextAttr and $0F) or (ord(Color) shl 4);
  if TextAttr<>oldAttr then
    SetConsoleTextAttribute(StdOut,TextAttr);
end;


procedure ConsoleWaitForEnterKey;
{$ifdef DELPHI5OROLDER}
begin
  readln;
end;
{$else}


  function KeyPressed(ExpectedKey: Word):Boolean;
  var lpNumberOfEvents: DWORD;
      lpBuffer: TInputRecord;
      lpNumberOfEventsRead : DWORD;
      nStdHandle: THandle;
  begin
    result := false;
    nStdHandle := GetStdHandle(STD_INPUT_HANDLE);
    lpNumberOfEvents := 0;
    GetNumberOfConsoleInputEvents(nStdHandle,lpNumberOfEvents);
    if lpNumberOfEvents<>0 then begin
      PeekConsoleInput(nStdHandle,lpBuffer,1,lpNumberOfEventsRead);
      if lpNumberOfEventsRead<>0 then
        if lpBuffer.EventType=KEY_EVENT then
          if lpBuffer.Event.KeyEvent.bKeyDown and
             ((ExpectedKey=0) or (lpBuffer.Event.KeyEvent.wVirtualKeyCode=ExpectedKey)) then
            result := true else
            FlushConsoleInputBuffer(nStdHandle) else
          FlushConsoleInputBuffer(nStdHandle);
    end;
  end;

var msg: TMsg;
begin
  while not KeyPressed(VK_RETURN) do begin
    {$ifndef LVCL}
    if GetCurrentThreadID=MainThreadID then
      CheckSynchronize{$ifdef WITHUXTHEME}(1000){$endif}  else
    {$endif}
      WaitMessage;
    while PeekMessage(msg,0,0,0,PM_REMOVE) do
      if Msg.Message=WM_QUIT then
        exit else begin
        TranslateMessage(Msg);
        DispatchMessage(Msg);
      end;
  end;
end;
{$endif}


{ TFileVersion }

constructor TFileVersion.Create(const FileName: TFileName;
  DefaultVersion: integer);
var Size, Size2: DWord;
................................................................................
function TFileVersion.Version32: integer;
begin
  result := Major shl 16+Minor shl 8+Release;
end;

{$else}

procedure TextColor(Color: TConsoleColor);
begin
  crt.TextColor(integer(Color));
end;

procedure TextBackground(Color: TConsoleColor);
begin
  crt.TextBackground(integer(Color));
end;

procedure ConsoleWaitForEnterKey;
begin
  Readln;
end;

const
  _SC_PAGE_SIZE = $1000;

{$endif MSWINDOWS}



................................................................................
end;

{$endif}

function TDynArray.Add(const Elem): integer;
begin
  result := Count;
  if Value=nil then
    exit; // avoid GPF if void
  SetCount(result+1);
  ElemCopy(Elem,pointer(PtrUInt(Value^)+PtrUInt(result)*ElemSize)^);
end;

function TDynArray.New: integer;
begin
  result := Count;
  if Value=nil then
    exit; // avoid GPF if void
  SetCount(result+1);
end;

procedure TDynArray.Insert(Index: Integer; const Elem);
var n: integer;
    P: PByteArray;
begin
  if Value=nil then
    exit; // avoid GPF if void
  n := Count;
  SetCount(n+1);
  if cardinal(Index)<cardinal(n) then begin
    P := pointer(PtrUInt(Value^)+PtrUInt(Index)*ElemSize);
    Move(P[0],P[ElemSize],cardinal(n-Index)*ElemSize);
    if ElemType<>nil then
      fillchar(P[0],ElemSize,0); // avoid GPF in ElemCopy() below
  end else
    // Index>=Count -> add at the end
    P := pointer(PtrUInt(Value^)+PtrUInt(n)*ElemSize);
  ElemCopy(Elem,P^);
end;

procedure TDynArray.Clear;
begin
  SetCount(0);
end;

procedure TDynArray.Delete(aIndex: Integer);
var n, len: integer;
    P: PAnsiChar;
begin
  if Value=nil then
    exit; // avoid GPF if void
  n := Count;
  if cardinal(aIndex)>=cardinal(n) then
    exit; // out of range
  dec(n);
  P := pointer(PtrUInt(Value^)+PtrUInt(aIndex)*ElemSize);
  if ElemType<>nil then
    _Finalize(P,ElemType);
  if n>aIndex then begin
    len := cardinal(n-aIndex)*ElemSize;
    move(P[ElemSize],P[0],len);
    if ElemType<>nil then // avoid GPF
      fillchar(P[len],ElemSize,0);
................................................................................
  end;
  SetCount(n);
end;

function TDynArray.ElemPtr(aIndex: integer): pointer;
begin
  result := nil;
  if (Value=nil) or (Value^=nil) then
    exit;
  if fCountP<>nil then begin
    if cardinal(aIndex)>=PCardinal(fCountP)^ then
      exit;
  end else
    {$ifdef FPC}
    if cardinal(aIndex)>=cardinal(DynArrayLength(Value^)) then
    {$else}
    if cardinal(aIndex)>=PCardinal(PtrUInt(Value^)-sizeof(PtrInt))^ then
    {$endif}
      exit;
  result := pointer(PtrUInt(Value^)+PtrUInt(aIndex)*ElemSize);
end;

function TDynArray.GetCount: integer;
begin
  if Value<>nil then
    if fCountP=nil then
      if PtrInt(Value^)<>0 then
        {$ifdef FPC}
        result := DynArrayLength(Value^) else
        {$else}
        result := PInteger(PtrUInt(Value^)-sizeof(PtrInt))^ else
        {$endif}
        result := 0 else
      result := fCountP^ else
    result := 0; // avoid GPF if void
end;

procedure Exchg(P1,P2: PAnsiChar; max: integer);
................................................................................
    P1, P2: PAnsiChar;
    c: AnsiChar;
    i64: Int64;
begin
  n := Count-1;
  if n>0 then begin
    siz := ElemSize;
    P1 := Value^;
    case siz of
    1: begin
      // optimized version for TByteDynArray and such
      P2 := P1+n;
      for i := 1 to n shr 1 do begin
        c := P1^;
        P1^ := P2^;
................................................................................
end;

procedure TDynArray.SaveToStream(Stream: TStream);
var Posi, PosiEnd: Integer;
    MemStream: TCustomMemoryStream absolute Stream;
    tmp: RawByteString;
begin
  if (Value=nil) or (Stream=nil) then
    exit; // avoid GPF if void
  if Stream.InheritsFrom(TCustomMemoryStream) then begin
    Posi := MemStream.Seek(0,soFromCurrent);
    PosiEnd := Posi+SaveToLength;
    if PosiEnd>MemStream.Size then
      MemStream.Size := PosiEnd;
    if SaveTo(PAnsiChar(MemStream.Memory)+Posi)-MemStream.Memory<>PosiEnd then
................................................................................
end;

function TDynArray.SaveTo(Dest: PAnsiChar): PAnsiChar;
var i, n, LenBytes: integer;
    P: PAnsiChar;
    FieldTable: PFieldTable;
begin
  if Value=nil then begin
    result := Dest;
    exit; // avoid GPF if void
  end;
  // first store the element size+type to check for the format (name='' mostly)
  Dest := pointer(ToVarUInt32(ElemSize,pointer(Dest)));
  if ElemType=nil then
    Dest^ := #0 else
................................................................................
  if n=0 then begin
    result := Dest;
    exit;
  end;
  inc(Dest,sizeof(Cardinal)); // leave space for Hash32 checksum
  result := Dest;
  // store dynamic array elements content
  P := Value^;
  if ElemType=nil then begin
    // binary types: store as once
    n := n*integer(ElemSize);
    move(P^,Dest^,n);
    inc(Dest,n);
  end else
    case PTypeKind(ElemType)^ of
................................................................................
  result := Dest;
end;

function TDynArray.SaveToLength: integer;
var i,n,L: integer;
    P: PAnsiChar;
begin
  if Value=nil then begin
    result := 0;
    exit; // avoid GPF if void
  end;
  n := Count;
  result := ToVarUInt32Length(ElemSize)+ToVarUInt32Length(n)+1;
  if n=0 then
    exit;
  if ElemType=nil then
    inc(result,integer(ElemSize)*n) else begin
    P := Value^;
    case PTypeKind(ElemType)^ of
    tkLString, tkWString:
      for i := 1 to n do begin
        if PPtrUInt(P)^=0 then
          inc(result) else
          inc(result,ToVarUInt32LengthWithData(PInteger(PPtrUInt(P)^-sizeof(integer))^));
        inc(P,sizeof(PtrUInt));
................................................................................
end;

const
  PTRSIZ = sizeof(Pointer);
  KNOWNTYPE_SIZE: array[TDynArrayKind] of byte = (
    0, 1, 2, 4,4,4, 8,8,8,8,8, PTRSIZ,PTRSIZ,PTRSIZ,PTRSIZ,PTRSIZ,
    {$ifndef NOVARIANTS}sizeof(Variant),{$endif} 0);
    





function TDynArray.ToKnownType: TDynArrayKind;
var FieldTable: PFieldTable;
label Bin, Rec;
begin
  if fKnownType<>djNone then begin
    result := fKnownType;
    exit;
................................................................................
    T: TDynArrayKind;
    wasString, expectedString, isValid: boolean;
    EndOfObject: AnsiChar;
    Val: PUTF8Char;
    CustomReader: TDynArrayJSONCustomReader;
begin // code below must match TTextWriter.AddDynArrayJSON()
  result := nil;
  if (P=nil) or (Value=nil) then
    exit;
  P := GotoNextNotSpace(P);
  if P^<>'[' then
    exit;
  repeat inc(P) until P^<>' ';
  n := JSONArrayCount(P);
  if n<0 then
................................................................................
      exit; // invalid content
  end else begin
    Count := n; // fast allocation of the whole dynamic array memory at once
    case T of
    {$ifndef NOVARIANTS}
    djVariant:
      for i := 0 to n-1 do 
        P := VariantLoadJSON(PVariantArray(Value^)^[i],P,@EndOfObject,@JSON_OPTIONS[true]);
    {$endif}
    djCustom: begin
      Val := Value^;
      for i := 1 to n do begin
        P := CustomReader(P,Val^,isValid);
        if not isValid then
          exit;
        EndOfObject := P^; // ',' or ']' for the last item of the array
        inc(P);
        inc(Val,ElemSize);
................................................................................
    else begin
      expectedString := (T in [djTimeLog..djSynUnicode]);
      for i := 0 to n-1 do begin
        Val := GetJSONField(P,P,@wasString,@EndOfObject);
        if (Val=nil) or (wasString<>expectedString) then
          exit;
        case T of
        djByte:     PByteArray(Value^)^[i] := GetCardinal(Val);
        djWord:     PWordArray(Value^)^[i] := GetCardinal(Val);
        djInteger:  PIntegerArray(Value^)^[i] := GetInteger(Val);
        djCardinal: PCardinalArray(Value^)^[i] := GetCardinal(Val);
        djSingle:   PSingleArray(Value^)^[i] := GetExtended(Val);
        djInt64:    SetInt64(Val,PInt64Array(Value^)^[i]);
        djTimeLog:  PInt64Array(Value^)^[i] := Iso8601ToTimeLogPUTF8Char(Val,0);
        djDateTime: Iso8601ToDateTimePUTF8CharVar(Val,0,TDateTime(PDoubleArray(Value^)^[i]));
        djDouble:   PDoubleArray(Value^)^[i] := GetExtended(Val);
        djCurrency: PInt64Array(Value^)^[i] := StrToCurr64(Val);
        djRawUTF8:  RawUTF8(PPointerArray(Value^)^[i]) := Val;
        djWinAnsi:  WinAnsiConvert.UTF8BufferToAnsi(Val,StrLen(Val),RawByteString(PPointerArray(Value^)^[i]));
        djString:   UTF8DecodeToString(Val,StrLen(Val),string(PPointerArray(Value^)^[i]));
        djWideString: UTF8ToWideString(Val,StrLen(Val),WideString(PPointerArray(Value^)^[i]));
        djSynUnicode: UTF8ToSynUnicode(Val,StrLen(Val),SynUnicode(PPointerArray(Value^)^[i]));
        end;
      end;
    end;
    end;
  end;
  if aEndOfObject<>nil then
    aEndOfObject^ := EndOfObject;
................................................................................
  // check stored element size+type
  if Source=nil then begin
    Clear;
    result := nil;
    exit;
  end;
  FromVarUInt32(PByte(Source)); // ignore StoredElemSize to be Win32/64 compatible
  if (Value=nil) or
     //((ElemSize<>sizeof(pointer)) and (StoredElemSize<>ElemSize)) or
     ((ElemType=nil) and (Source^<>#0) or
     ((ElemType<>nil) and (Source^=#0{<>PAnsiChar(ElemType)^}))) then begin
     // ignore ElemType^ to be cross-FPC/Delphi compatible
    result := nil; // invalid Source content
    exit;
  end;
................................................................................
    result := Source;
    exit;
  end;
  // retrieve security checksum
  Hash := pointer(Source);
  inc(Source,sizeof(cardinal));
  // retrieve dynamic array elements content
  P := Value^;
  if ElemType=nil then begin
    // binary type was stored as once
    n := n*integer(ElemSize);
    move(Source^,P^,n);
    inc(Source,n);
  end else
  case PTypeKind(ElemType)^ of
................................................................................
      aCompare: TDynArraySortCompare): integer;
var n, L, cmp: integer;
    P: PAnsiChar;
begin
  n := Count;
  if (@aCompare<>nil) and (n>0) then begin
    dec(n);
    P := Value^;
    if (n>10) and (length(aIndex)>n) then begin
      // array should be sorted -> use fast binary search
      L := 0;
      repeat
        result := (L+n) shr 1;
        cmp := aCompare(P[cardinal(aIndex[result])*ElemSize],Elem);
        if cmp=0 then
................................................................................
end;

function TDynArray.FindAndFill(var Elem; aIndex: PIntegerDynArray=nil;
  aCompare: TDynArraySortCompare=nil): integer;
begin
  result := FindIndex(Elem,aIndex,aCompare);
  if result>=0 then // if found, fill Elem with the matching item
    ElemCopy(PAnsiChar(Value^)[cardinal(result)*ElemSize],Elem);
end;

function TDynArray.FindAndDelete(var Elem; aIndex: PIntegerDynArray=nil;
  aCompare: TDynArraySortCompare=nil): integer;
begin
  result := FindIndex(Elem,aIndex,aCompare);
  if result>=0 then
................................................................................
end;

function TDynArray.FindAndUpdate(const Elem; aIndex: PIntegerDynArray=nil;
  aCompare: TDynArraySortCompare=nil): integer;
begin
  result := FindIndex(Elem,aIndex,aCompare);
  if result>=0 then // if found, fill Elem with the matching item
    ElemCopy(Elem,PAnsiChar(Value^)[cardinal(result)*ElemSize]);
end;

function TDynArray.FindAndAddIfNotExisting(const Elem; aIndex: PIntegerDynArray=nil;
  aCompare: TDynArraySortCompare=nil): integer;
begin
  result := FindIndex(Elem,aIndex,aCompare);
  if result<0 then
................................................................................
function TDynArray.Find(const Elem): integer;
var n, L, cmp: integer;
    P: PAnsiChar;
begin
  n := Count;
  if (@fCompare<>nil) and (n>0) then begin
    dec(n);
    P := Value^;
    if fSorted and (n>10) then begin
      // array is sorted -> use fast binary search
      L := 0;
      repeat
        result := (L+n) shr 1;
        cmp := fCompare(P[cardinal(result)*ElemSize],Elem);
        if cmp=0 then
................................................................................
    L := I;
  until I >= R;
end;

procedure TDynArray.Sort;
var QuickSort: TDynArrayQuickSort;
begin
  if (@fCompare<>nil) and (Value<>nil) and (Value^<>nil) then begin
    Quicksort.Compare := @fCompare;
    Quicksort.Value := Value^;
    Quicksort.ElemSize := ElemSize;
    Quicksort.QuickSort(0,Count-1);
    fSorted := true;
  end;
end;

procedure TDynArray.CreateOrderedIndex(var aIndex: TIntegerDynArray;
  aCompare: TDynArraySortCompare);
var QuickSort: TDynArrayQuickSort;
    n: integer;
begin
  if (@aCompare<>nil) and (Value<>nil) and (Value^<>nil) then begin
    n := Count;
    if length(aIndex)<n then begin
      SetLength(aIndex,n);
      FillIncreasing(pointer(aIndex),0,n);
    end;
    Quicksort.Compare := @aCompare;
    Quicksort.Value := Value^;
    Quicksort.ElemSize := ElemSize;
    Quicksort.Index := pointer(aIndex);
    Quicksort.QuickSortIndexed(0,n-1);
  end;
end;

function TDynArray.ElemEquals(const A,B): boolean;
................................................................................
begin
  result := false;
  if ArrayType<>B.ArrayType then
    exit; // array types shall match
  n := Count;
  if n<>B.Count then
    exit;
  P1 := Value^;
  P2 := B.Value^;
  if @fCompare<>nil then // if a customized comparison is available, use it
    for i := 1 to n do
      if fCompare(P1^,P2^)<>0 then
        exit else begin
        inc(P1,ElemSize);
        inc(P2,ElemSize);
      end else
................................................................................
end;
{$endif}

function TDynArray.IndexOf(const Elem): integer;
var P: pointer;
    max: integer;
begin
  if Value=nil then begin
    result := -1;
    exit; // avoid GPF if void
  end;
  max := Count-1;
  P := Value^;
  if @Elem<>nil then
  if ElemType=nil then
  case ElemSize of
    // optimized versions for arrays of byte,word,integer,Int64,Currency,Double
    1: for result := 0 to max do
         if PByteArray(P)^[result]=byte(Elem) then exit;
    2: for result := 0 to max do
................................................................................
  result := -1;
end;

procedure TDynArray.Init(aTypeInfo: pointer; var aValue; aCountPointer: PInteger=nil);
var Typ: PDynArrayTypeInfo absolute aTypeInfo;
begin
  fTypeInfo := aTypeInfo;
  Value := @aValue;
  if Typ^.Kind<>tkDynArray then
    raise ESynException.CreateUTF8('Not a dynamic array: %',[PShortString(@Typ^.NameLen)^]);
  {$ifdef FPC_REQUIRES_PROPER_ALIGNMENT}
  Typ := GetFPCAlignPtr(Typ);
  {$else}
  inc(PtrUInt(Typ),Typ^.NameLen);
  {$endif}
................................................................................
  fCompare := Comp;
  fKnownType := aKind;
  fKnownSize := KNOWNTYPE_SIZE[aKind];
end;

procedure TDynArray.Void;
begin
  Value := nil;
end;

function TDynArray.IsVoid: boolean;
begin
  result := Value=nil;
end;

procedure _DynArrayClear(var a: Pointer; typeInfo: Pointer);
{$ifdef FPC}
  [external name 'FPC_DYNARRAY_CLEAR'];
{$else}
asm
................................................................................
    OldLength, NeededSize, minLength: PtrUInt;
    pp: pointer;
begin // this method is faster than default System.DynArraySetLength() function
  // check that new array length is not just a hidden finalize
  if NewLength=0 then begin
    {$ifndef NOVARIANTS}
    if ArrayType=TypeInfo(TVariantDynArray) then
      VariantDynArrayClear(TVariantDynArray(Value^)) else
    {$endif}
      _DynArrayClear(Value^,ArrayType);
    exit;
  end;
  // retrieve old length
  p := Value^;
  if p<>nil then begin
    dec(PtrUInt(p),Sizeof(TDynArrayRec)); // p^ = start of heap object
    OldLength := p^.length;
  end else
    OldLength := 0;
  // calculate the needed size of the resulting memory structure on heap
  NeededSize := NewLength*ElemSize+Sizeof(TDynArrayRec);
................................................................................
    GetMem(p,neededSize);
    minLength := oldLength;
    if minLength>newLength then
      minLength := newLength;
    if ElemType<>nil then begin
      pp := pa+Sizeof(TDynArrayRec);
      FillChar(pp^,minLength*elemSize,0);
      CopyArray(pp,Value^,ElemType,minLength)
    end else
      Move(Value^,pa[Sizeof(TDynArrayRec)],minLength*elemSize);
  end;
  // set refCnt=1 and new length to the heap memory structure
  with p^ do begin
    refCnt := 1;
    {$ifdef FPC}
    high := newLength-1;
    {$else}
................................................................................
    length := newLength;
    {$endif}
  end;
  Inc(PtrUInt(p),Sizeof(p^));
  // reset new allocated elements content to zero 
  OldLength := OldLength*elemSize;
  FillChar(pa[OldLength],neededSize-OldLength-Sizeof(TDynArrayRec),0);
  Value^ := p;
end;

procedure TDynArray.SetCount(aCount: integer);
const MINIMUM_SIZE = 64;
var capa, delta: integer;
begin
  fSorted := false;
  if Value=nil then
    exit; // avoid GPF if void
  if fCountP<>nil then begin
    delta := aCount-fCountP^;
    if delta=0 then
      exit;
    fCountP^ := aCount;
    if PtrInt(Value^)=0 then begin
      // no capa yet
      if (delta>0) and (aCount<MINIMUM_SIZE) then
        aCount := MINIMUM_SIZE; // reserve some minimal space for Add()
    end else begin
      capa := DynArrayLength(Value^);
      if delta>0 then begin
        // size-up -> grow by chunks
        if capa>=fCountP^ then
          exit; // no need to grow
        inc(capa,capa shr 2);
        if capa<fCountP^ then
          aCount := fCountP^ else
................................................................................
  end;
  // no external Count, array size-down or array up-grow -> realloc
  InternalSetLength(aCount);
end;

function TDynArray.GetCapacity: integer;
begin // capacity := length(DynArray)
  if (Value<>nil) and (PtrInt(Value^)<>0) then
    result := DynArrayLength(Value^) else
    result := 0;
end;

procedure TDynArray.SetCapacity(aCapacity: integer);
begin
  if Value=nil then
    exit; // avoid GPF if void
  if fCountP<>nil then
    if fCountP^>aCapacity then
      fCountP^ := aCapacity;
  InternalSetLength(aCapacity);
end;

................................................................................
    fSorted := false;
  end;
end;

procedure TDynArray.Copy(Source: TDynArray);
var n: Cardinal;
begin
  if (Value=nil) or (Source.Value=nil) or (ArrayType<>Source.ArrayType) then
    Exit;
  SetCapacity(Source.Capacity);
  n := Source.Count;
  if ElemType=nil then
    move(Source.Value^^,Value^^,n*ElemSize) else
    CopyArray(Value^,Source.Value^,ElemType,n);
end;

procedure TDynArray.CopyFrom(const Source; MaxElem: integer);
var SourceDynArray: TDynArray;
begin
  SourceDynArray.Init(fTypeInfo,pointer(@Source)^);
  SourceDynArray.fCountP := @MaxElem; // would set Count=0 at Init()
................................................................................
end;

procedure TDynArray.Slice(var Dest; aCount: Cardinal; aFirstIndex: cardinal=0);
var n: Cardinal;
    D: PPointer;
    P: PAnsiChar;
begin
  if Value=nil then
    exit; // avoid GPF if void
  n := Count;
  if aFirstIndex>=n then
    aCount := 0 else
  if aCount>=n-aFirstIndex then
    aCount := n-aFirstIndex;
  DynArray(ArrayType,Dest).InternalSetLength(aCount);
  D := @Dest;
  if aCount>0 then begin
    P := PAnsiChar(Value^)+aFirstIndex*ElemSize;
    if ElemType=nil then
      move(P^,D^^,aCount*ElemSize) else
      CopyArray(D^,P,ArrayType,aCount);
  end;
end;

procedure TDynArray.AddArray(const DynArray; aStartIndex: integer=0; aCount: integer=-1);
var DynArrayCount, n: integer;
    D: PPointer;
    PS,PD: pointer;
begin
  if Value=nil then
    exit; // avoid GPF if void
  D := @DynArray;
  if D^=nil then  // inline GetCount
    DynArrayCount := 0 else
    DynArrayCount := DynArrayLength(D^);
  if aStartIndex>=DynArrayCount then
    exit; // nothing to copy
................................................................................
  if (aCount<0) or (cardinal(aStartIndex+aCount)>cardinal(DynArrayCount)) then
    aCount := DynArrayCount-aStartIndex;
  if aCount<=0 then
    exit;
  n := Count;
  SetCount(n+aCount);
  PS := pointer(PtrUInt(D^)+cardinal(aStartIndex)*ElemSize);
  PD := pointer(PtrUInt(Value^)+cardinal(n)*ElemSize);
  if ElemType=nil then
    move(PS^,PD^,cardinal(aCount)*ElemSize) else
    CopyArray(PD,PS,ArrayType,aCount);
end;

procedure TDynArray.ElemClear(var Elem);
begin
................................................................................
end;

procedure TDynArrayHashed.SetCapacity(aCapacity: Integer);
begin
  InternalDynArray.SetCapacity(aCapacity);
end;

function TDynArrayHashed.Value: PPointer;
begin
  result := InternalDynArray.Value;
end;

function TDynArrayHashed.ElemSize: PtrUInt;
begin
  result := InternalDynArray.ElemSize;
end;

................................................................................
    repeat
      aName := aName_+UInt32ToUTF8(j);
      ndx := FindHashedForAdding(aName,added);
      inc(j);
    until added;
  end;
  assert(ndx=Count-1);
  result := PAnsiChar(Value^)+cardinal(ndx)*ElemSize;
  PRawUTF8(result)^ := aName; // store unique name at 1st elem position
end;

function TDynArrayHashed.AddUniqueName(const aName: RawUTF8;
  ExceptionMsg: PUTF8Char; const ExceptionArgs: array of const): pointer;
var ndx: integer;
    added: boolean;
begin
  ndx := FindHashedForAdding(aName,added);
  if added then begin
    assert(ndx=Count-1);
    result := PAnsiChar(Value^)+cardinal(ndx)*ElemSize;
    PRawUTF8(result)^ := aName; // store unique name at 1st elem position
  end else
    if ExceptionMsg=nil then
      raise ESynException.CreateUTF8('Duplicated "%" name',[aName]) else
      raise ESynException.CreateUTF8(ExceptionMsg,ExceptionArgs);
end;

................................................................................
function TDynArrayHashed.FindHashedAndFill(var Elem): integer;
var P: PAnsiChar;
begin
  result := HashFind(HashOneFromTypeInfo(Elem),Elem);
  if result<0 then
    result := -1 else begin
    // copy from dynamic array found entry into Elem = Fill
    P := PAnsiChar(Value^)+cardinal(result)*ElemSize;
    if ElemType=nil then
      move(P^,Elem,ElemSize) else
      CopyArray(@Elem,P,ElemType,1);
  end;
end;

function TDynArrayHashed.FindHashedAndUpdate(var Elem; AddIfNotExisting: boolean): integer;
................................................................................
  if aHashCode=HASH_VOID then
    aHashCode := HASH_ONVOIDCOLISION; // as in HashFind() -> for HashAdd() below
  result := HashFind(aHashCode,Elem);
  if result<0 then
    if AddIfNotExisting then begin
      // not existing -> add as new element
      HashAdd(Elem,aHashCode,result); // ReHash only if necessary
      P := PAnsiChar(Value^)+cardinal(result)*ElemSize;
      if ElemType=nil then
        move(Elem,P^,ElemSize) else
        CopyArray(P,@Elem,ElemType,1);
    end else
      result := -1 else begin
    // copy from Elem into dynamic array found entry = Update
    P := PAnsiChar(Value^)+cardinal(result)*ElemSize;
    if ElemType=nil then
      move(Elem,P^,ElemSize) else
      CopyArray(P,@Elem,ElemType,1);
    ReHash;
  end;
end;

................................................................................
  result := (aHashCode-1) and (n-1); // fHashs[] has a power of 2 length
  first := result;
  repeat
    with fHashs[result] do
    if Hash=aHashCode then
      if not Assigned(fEventCompare) then
        if @{$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$endif}fCompare<>nil then begin
          if {$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$endif}fCompare(PAnsiChar(Value^)[Index*ElemSize],Elem)=0 then begin
            result := Index;
            exit; // found -> returns index in dynamic array
          end;
        end else begin
        if {$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$endif}ElemEquals(PAnsiChar(Value^)[Index*ElemSize],Elem) then begin
          result := Index;
          exit; // found -> returns index in dynamic array
        end;
      end else begin
        if fEventCompare(PAnsiChar(Value^)[Index*ElemSize],Elem)=0 then begin
          result := Index;
          exit; // found -> returns index in dynamic array
        end;
      end else
    if Hash=HASH_VOID then begin
      result := -(result+1);
      exit; // not found -> returns void index in fHashs[] as negative
................................................................................

function TDynArrayHashed.GetHashFromIndex(aIndex: Integer): Cardinal;
var P: pointer;
begin
  if cardinal(aIndex)>=cardinal(Count) then
    result := 0 else begin
    // it's faster to rehash than to loop in fHashs[].Index values
    P := PAnsiChar(Value^)+cardinal(aIndex)*ElemSize;
    result := HashOneFromTypeInfo(P^);
    if result=HASH_VOID then
      result := HASH_ONVOIDCOLISION; // 0 means void slot in the loop below
  end;
end;

function TDynArrayHashed.HashOneFromTypeInfo(const Elem): cardinal;
................................................................................
var i, n, ndx: integer;
    P: PAnsiChar;
    aHashCode: cardinal;
begin
  HashInit;
  n := Count;
  if n>0 then begin // avoid GPF after TDynArray.Clear call (Count=0)
    P := Value^;
    for i := 0 to n-1 do begin
      if @aHasher=nil then
        aHashCode := HashOneFromTypeInfo(P^) else
        aHashCode := aHasher(P^);
      if aHashCode=HASH_VOID then
        aHashCode := HASH_ONVOIDCOLISION; // 0 means void slot in the loop below
      ndx := HashFind(aHashCode,P^);
................................................................................
    Add('[',']');
    exit;
  end;
  if GlobalJSONCustomParsers.DynArraySearch(
      aDynArray.ArrayType,aDynArray.ElemType,customWriter,@customParser) then
    T := djCustom else
    T := aDynArray.ToKnownType;
  P := aDynArray.Value^;
  Add('[');
  case T of
  djNone: begin
    tmp := aDynArray.SaveTo;
    WrBase64(pointer(tmp),length(tmp),true); // magic=true
  end;
  djCustom: begin
................................................................................
  dec(B);
end;

function TTextWriter.LastChar: AnsiChar;
begin
  result := B^;
end;






procedure TTextWriter.CancelLastComma;
begin
  if B^=',' then
    dec(B);
end;

................................................................................
  P := @PByteArray(fTempBuf)[fEchoStart];
  while (L>0) and (P[L-1] in [10,13]) do // trim right CR/LF chars
    dec(L);
  LI := length(fEchoBuf); // faster append to fEchoBuf
  SetLength(fEchoBuf,LI+L);
  Move(P^,PByteArray(fEchoBuf)[LI],L);
end;







{ TJSONWriter }

procedure TJSONWriter.CancelAllVoid;
const VOIDARRAY: PAnsiChar = '[]'#10;
      VOIDFIELD: PAnsiChar = '{"FieldCount":0}';
................................................................................
  MinLowerCount := 1;
  MinUpperCount := 1;
  MaxSpaceCount := 0;
  // read custom parameters
  inherited;
end;













































































































{ ************ Unit-Testing classes and functions }

function KB(bytes: Int64): RawUTF8;
begin
  if bytes>=1024*1024 then begin
    if bytes>=1024*1024*1024 then begin
................................................................................
  {$endif CONDITIONALEXPRESSIONS}
{$endif}
{$ifdef CPU64}
  +' 64 bit'
{$endif}
end;

{ TSynTest }

procedure TSynTest.Add(aMethod: TSynTestEvent; const aName: string);
var i: integer;
begin
  if self=nil then
    exit; // avoid GPF
  i := Length(fTests);
  SetLength(fTests,i+1);
  fTests[i].TestName := aName;
  fTests[i].TestNameUTF8 := StringToUTF8(fIdent+' - '+aName);
  fTests[i].Method := aMethod;
end;

constructor TSynTest.Create(const Ident: string);

  procedure AddParentsFirst(C: TClass);
  type
    TMethodInfo =
      {$ifndef FPC_REQUIRES_PROPER_ALIGNMENT}
      packed
      {$endif FPC_REQUIRES_PROPER_ALIGNMENT}
      record
    {$ifdef FPC}
      Name: PShortString;
      Addr: Pointer;
    {$else}
      Len: Word;
      Addr: Pointer;
      Name: ShortString;
    {$endif}
    end;
  var Table: {$ifdef FPC}PCardinalArray;{$else}PWordArray;{$endif}
      M: ^TMethodInfo;
      Method: TMethod;
      i: integer;
      text: RawUTF8;
  begin
    if C=nil then
      exit;
    AddParentsFirst(C.ClassParent); // put children published methods afterward
    Table := PPointer(PtrInt(C)+vmtMethodTable)^;
    if Table=nil then
      exit;
    Method.Data := self;
    M := @Table^[1];
    for i := 1 to Table^[0] do begin // Table^[0] = methods count
      inc(fInternalTestsCount);
      Method.Code := M^.Addr;
      text := M^.Name{$ifdef FPC}^{$endif};
      if text[1]='_' then
        delete(text,1,1) else
        text := UnCamelCase(text);
      Add(TSynTestEvent(Method),Ansi7ToString(text));
      {$ifdef FPC}
      inc(M);
      {$else}
      inc(PtrInt(M),M^.Len);
      {$endif}
    end;
  end;

var tmp: RawUTF8;
begin
  if Ident<>'' then
    fIdent := Ident else begin
    tmp := RawUTF8(ClassName);
    if IdemPChar(Pointer(tmp),'TSYN') then
      if IdemPChar(Pointer(tmp),'TSYNTEST') then
        Delete(tmp,1,8) else
      Delete(tmp,1,4) else
    if IdemPChar(Pointer(tmp),'TTEST') then
      Delete(tmp,1,5) else
    if tmp[1]='T' then
      Delete(tmp,1,1);
    fIdent := string(UnCamelCase(tmp));
  end;
  AddParentsFirst(PPointer(Self)^); // use recursion for adding
end;

function TSynTest.GetCount: Integer;
begin
  if self=nil then
    result := 0 else
    result := length(fTests);
end;

function TSynTest.GetIdent: string;
begin
  if self=nil then
    result := '' else
    result := fIdent;
end;

function TSynTest.GetTestMethod(Index: integer): TSynTestEvent;
begin
  if Cardinal(Index)>=Cardinal(length(fTests)) then
    result := nil else
    result := fTests[Index].Method;
end;

function TSynTest.GetTestName(Index: integer): string;
begin
  if (self=nil) or (Cardinal(Index)>=Cardinal(length(fTests))) then
    result := '' else
    result := fTests[Index].TestName;
end;


{ TSynTestCase }

procedure TSynTestCase.Check(condition: Boolean; const msg: string);
begin
  Inc(fAssertions);
  if not condition then
    TestFailed(msg);
end;

function TSynTestCase.CheckFailed(condition: Boolean; const msg: string): Boolean;
begin
  Inc(fAssertions);
  if condition then
    result := false else begin
    TestFailed(msg);
    result := true;
  end;
end;

function TSynTestCase.CheckNot(condition: Boolean; const msg: string): Boolean;
begin
  result := CheckFailed(not condition, msg);
end;

function TSynTestCase.CheckSame(const Value1, Value2, Precision: double;
  const msg: string): Boolean;
begin
  result := CheckFailed(SameValue(Value1,Value2,Precision),msg);
end;

procedure TSynTestCase.CheckMatchAny(const Value: RawUTF8;
  const Values: array of RawUTF8; CaseSentitive,ExpectedResult: Boolean; const msg: string);
begin
  Check((FindRawUTF8(Values,Value,CaseSentitive)>=0)=ExpectedResult);
end;

constructor TSynTestCase.Create(Owner: TSynTests; const Ident: string);
begin
  inherited Create(Ident);
  fOwner := Owner;
end;

procedure TSynTestCase.CleanUp;
begin
  // do nothing by default
end;

destructor TSynTestCase.Destroy;
begin
  CleanUp;
  inherited;
end;

class function TSynTestCase.RandomString(CharCount: Integer): RawByteString;
var V: cardinal;
    P: PAnsiChar;
begin
  SetString(result,nil,CharCount);
  P := pointer(Result);
  while CharCount>0 do begin
    if CharCount>5 then begin
      V := Random(maxInt); // fast: one random compute per 5 chars
      P[0] := AnsiChar(32+V and 127); V := V shr 7;
      P[1] := AnsiChar(32+V and 127); V := V shr 7;
      P[2] := AnsiChar(32+V and 127); V := V shr 7;
      P[3] := AnsiChar(32+V and 127); V := V shr 7;
      P[4] := AnsiChar(65+V);
      Inc(P,5);
      dec(CharCount,5);
    end else begin
      P^ := AnsiChar(32+Random(224));
      inc(P);
      dec(CharCount);
    end;
  end;
end;

class function TSynTestCase.RandomAnsi7(CharCount: Integer): RawByteString;
var V: cardinal;
    P: PAnsiChar;
begin
  SetString(result,nil,CharCount);
  P := pointer(Result);
  while CharCount>0 do begin
    if CharCount>5 then begin
      V := Random(maxInt); // fast: one random compute per 5 chars
      P[0] := AnsiChar(32+V mod 94); V := V div 94;
      P[1] := AnsiChar(32+V mod 94); V := V div 94;
      P[2] := AnsiChar(32+V mod 94); V := V div 94;
      P[3] := AnsiChar(32+V mod 94); V := V div 94;
      P[4] := AnsiChar(32+V mod 94);
      Inc(P,5);
      dec(CharCount,5);
    end else begin
      P^ := AnsiChar(32+Random(94));
      inc(P);
      dec(CharCount);
    end;
  end;
end;

class function TSynTestCase.RandomUTF8(CharCount: Integer): RawUTF8;
begin
  result := WinAnsiToUtf8(WinAnsiString(RandomString(CharCount)));
end;

class function TSynTestCase.RandomUnicode(CharCount: Integer): SynUnicode;
begin
  result := WinAnsiConvert.AnsiToUnicodeString(RandomString(CharCount));
end;

class function TSynTestCase.RandomTextParagraph(WordCount: Integer;
  LastPunctuation: AnsiChar; const RandomInclude: RawUTF8): RawUTF8;
type TKind = (space, comma, dot, question, paragraph);
const bla: array[0..4] of string[3]=('bla','ble','bli','blo','blu');
      endKind = [dot,paragraph,question];
var n: integer;
    WR: TTextWriter;
    s: string[3];
    last: TKind;
begin
  WR := TTextWriter.CreateOwnedStream;
  try
    last := paragraph;
    while WordCount>0 do begin
      for n := 0 to random(4) do begin
         s := bla[random(5)];
        if last in endKind then
          s[1] := upcase(s[1]);
        WR.AddShort(s);
        WR.Add(' ');
        dec(WordCount);
        last := space;
      end;
      WR.CancelLastChar;
      case random(100) of
      0..2: begin
        if RandomInclude<>'' then begin
          WR.Add(' ');
          WR.AddString(RandomInclude);
        end;
        last := space;
      end;
      3..40:  last := space;
      41..70: last := comma;
      71..85: last := dot;
      86..90: last := question;
      91..99: last := paragraph;
      end;
      case last of
      space: WR.Add(' ');
      comma: WR.Add(',',' ');
      dot:   WR.Add('.',' ');
      question:  WR.Add('?',' ');
      paragraph: WR.AddShort('.'#13#10);
      end;
    end;
    if not (last in endKind) then begin
      WR.AddShort('bla');
      if LastPunctuation<>' ' then
        WR.Add(LastPunctuation);
    end;
    WR.SetText(result);
  finally
    WR.Free;
  end;
end;

procedure TSynTestCase.TestFailed(const msg: string);
begin
  {$ifndef DELPHI5OROLDER}
  TSynLogTestLog.DebuggerNotify([msg],'Test failed %');
  {$endif}
  if Owner<>nil then // avoid GPF
    Owner.Failed(msg,self);
  Inc(fAssertionsFailed);
end;

procedure TSynTestCase.NotifyTestSpeed(const ItemName: string;
  ItemCount: integer; SizeInBytes: cardinal);
var Temp: TPrecisionTimer;
begin
  Temp := Owner.TestTimer;
  fRunConsole := format('%s%d %s in %s i.e. %d/s, aver. %s',
    [fRunConsole,ItemCount,ItemName,Temp.Stop,Temp.PerSec(ItemCount),
     Temp.ByCount(ItemCount)]);
  if SizeInBytes>0 then
    fRunConsole := format('%s, %s/s',[fRunConsole,KB(Temp.PerSec(SizeInBytes))]);
end;


{ TSynTests }

procedure TSynTests.AddCase(TestCase: TSynTestCase);
begin
  TestCase.fMethodIndex := fCurrentMethod;
  TestCase.fTestCaseIndex := fTestCase.Count-fCurrentMethodIndex;
  fTestCase.Add(TestCase);
end;

procedure TSynTests.AddCase(const TestCase: array of TSynTestCaseClass);
var i: integer;
begin
  for i := low(TestCase) to high(TestCase) do
    AddCase(TestCase[i].Create(self));
end;

procedure TSynTests.AddCase(TestCase: TSynTestCaseClass);
begin
  AddCase(TestCase.Create(self));
end;

function TSynTests.BeforeRun(const TestName: RawUTF8): IUnknown;
begin
  result := nil;
end;

constructor TSynTests.Create(const Ident: string);
begin
  inherited;
  fFailed := TStringList.Create;
  fTestCase := TObjectList.Create;
end;

{$I-}

procedure TSynTests.Color(aColor: TConsoleColor);
begin
{$ifdef MSWINDOWS}
  if (StdOut<>0) and (THandle(TTextRec(fSaveToFile).Handle)=StdOut) then
{$endif}
    TextColor(aColor);
end;

procedure TSynTests.CreateSaveToFile;
begin
  Assign(fSaveToFile,'');
  Rewrite(fSaveToFile);
  StdOut := TTextRec(fSaveToFile).Handle;
end;

destructor TSynTests.Destroy;
begin
  fFailed.Free;
  fTestCase.Free; // TObjectList will free all TSynTestCase instance
  if TTextRec(fSaveToFile).Handle<>0 then
    Close(fSaveToFile);
end;

procedure TSynTests.DuringRun(TestCaseIndex, TestMethodIndex: integer);
var C: TSynTestCase;
    Run,Failed: integer;
begin
  C := TestCase[TestCaseIndex];
  if C=nil then
    Exit;
  if TestMethodIndex<0 then begin
    Color(ccWhite);
    writeln(fSaveToFile,#13#10' ',C.MethodIndex+1,'.',C.TestCaseIndex+1,
      '. ',C.Ident,': ',#13);
  end else begin
    Run := C.Assertions-C.fAssertionsBeforeRun;
    Failed := C.AssertionsFailed-C.fAssertionsFailedBeforeRun;
    if Failed=0 then begin
      Color(ccGreen);
      Write(fSaveToFile,'  - ',C.TestName[TestMethodIndex],': ');
      if Run=0 then
        Write(fSaveToFile,'no assertion') else
      if Run=1 then
        Write(fSaveToFile,'1 assertion passed') else
        Write(fSaveToFile,IntToThousandString(Run),' assertions passed');
    end else begin
      Color(ccLightRed);
      Write(fSaveToFile,'!  - ',C.TestName[TestMethodIndex],': ',
        IntToThousandString(Failed),' / ',
        IntToThousandString(Run),' FAILED'); // ! to highlight the line
    end;
    Write(fSaveToFile,'  ',TestTimer.Stop);
    if C.fRunConsoleOccurenceNumber>0 then
      Write(fSaveToFile,'  ',
        IntToThousandString(TestTimer.PerSec(C.fRunConsoleOccurenceNumber)),'/s');
    if C.fRunConsoleMemoryUsed>0 then begin
      Write(fSaveToFile,'  ',KB(C.fRunConsoleMemoryUsed));
      C.fRunConsoleMemoryUsed := 0; // display only once
    end;
    Writeln(fSaveToFile,#13);
    if C.fRunConsole<>'' then begin
      Writeln(fSaveToFile,'     ',C.fRunConsole,#13);
      C.fRunConsole := '';
    end;
    if TestMethodIndex=C.Count-1 then begin
      if C.AssertionsFailed=0 then
        Color(ccLightGreen) else
        Color(ccLightRed);
      Write(fSaveToFile,'  Total failed: ',IntToThousandString(C.AssertionsFailed),
        ' / ',IntToThousandString(C.Assertions),'  - ',C.Ident);
      if C.AssertionsFailed=0 then
        Write(fSaveToFile,' PASSED') else
        Write(fSaveToFile,' FAILED');
      Writeln(fSaveToFile,'  ',TotalTimer.Stop,#13);
    end;
    Color(ccLightGray);
  end;
end;

procedure TSynTests.Failed(const msg: string; aTest: TSynTestCase);
begin
  fFailed.AddObject(msg,aTest);
end;

function TSynTests.GetFailedCase(Index: integer): TSynTestCase;
begin
  if (self=nil) or (cardinal(Index)>=cardinal(fFailed.Count)) then
    Result := nil else begin
    Index := integer(fFailed.Objects[index]);
    if Index<InternalTestsCount then
      Result := nil else
      Result := TSynTestCase(Index);
  end;
end;

function TSynTests.GetFailedCaseIdent(Index: integer): string;
begin
  if (self=nil) or (cardinal(Index)>=cardinal(fFailed.Count)) then
    Result := '' else begin
    Index := integer(fFailed.Objects[index]);
    if Index<InternalTestsCount then
      // if integer(Objects[]) is lower than InternalTestsCount, then
      // it is an index to the corresponding published method, and the Strings[]
      // contains the associated failure message
      Result := TestName[Index] else
      // if integer(Objects[]) is equal or higher than InternalTestsCount,
      // the Objects[] points to the TSynTestCase, and the Strings[] to the
      // associated failure message
      Result := TSynTestCase(Index).Ident;
  end;
end;

function TSynTests.GetFailedCount: integer;
begin
  if self=nil then
    result := 0 else
    result := fFailed.Count;
end;

function TSynTests.GetFailedMessage(Index: integer): string;
begin
  if (self=nil) or (cardinal(Index)>=cardinal(fFailed.Count)) then
    result := '' else
    result := fFailed.Strings[Index];
end;

function TSynTests.GetTestCase(Index: integer): TSynTestCase;
begin
  if (self=nil) or (cardinal(Index)>=cardinal(fTestCase.Count)) then
    Result := nil else
    Result := TSynTestCase(fTestCase[Index]);
end;

function TSynTests.GetTestCaseCount: Integer;
begin
  if self=nil then
    result := 0 else
    result := fTestCase.Count;
end;

function TSynTests.Run: Boolean;
var i,t,m: integer;
    Elapsed, Version: RawUTF8;
    C: TSynTestCase;
    ILog: IUnknown;
begin
  if TTextRec(fSaveToFile).Handle=0 then
    CreateSaveToFile;
  Color(ccLightCyan);
  Writeln(fSaveToFile,#13#10'   ',Ident,#13#10'  ',StringOfChar('-',length(Ident)+2));
{$ifdef MSWINDOWS}
  RunTimer.Start;
  ExeVersionRetrieve;
{$endif}
  C := nil;
  try
    // 1. register all test cases
    fTestCase.Clear;
    for m := 0 to Count-1 do begin
      C := pointer(m);
      fCurrentMethod := m;
      fCurrentMethodIndex := fTestCase.Count;
      // published methods will call AddCase() to register tests in fTestCase[]
       TSynTestEvent(TestMethod[m])();
    end;
    // 2. launch the tests
    Randomize;
    fFailed.Clear;
    fAssertions := 0;
    fAssertionsFailed := 0;
    m := -1;
    for i := 0 to fTestCase.Count-1 do begin
      C := TestCase[i];
      if C.MethodIndex<>m then begin
        m := C.MethodIndex;
        Color(ccWhite);
        writeln(fSaveToFile,#13#10#13#10,m+1,'. ',TestName[m]);
        Color(ccLightGray);
      end;
      DuringRun(i,-1);
      C.fAssertions := 0; // reset assertions count
      C.fAssertionsFailed := 0;
      TotalTimer.Start;
      for t := 0 to C.Count-1 do begin
        C.fAssertionsBeforeRun := C.fAssertions;
        C.fAssertionsFailedBeforeRun := C.fAssertionsFailed;
        C.fRunConsoleOccurenceNumber := fRunConsoleOccurenceNumber;
        fCurrentMethod := i;
        fCurrentMethodIndex := t;
        ILog := BeforeRun(C.fTests[t].TestNameUTF8);
        TestTimer.Start;
        TSynTestEvent(C.TestMethod[t])(); // run tests + Check() and TestFailed()
        ILog := nil; // will trigger logging leave method e.g.
        DuringRun(i,t);
      end;
      C.CleanUp; // should be done before Destroy call
      inc(fAssertions,C.fAssertions); // compute global assertions count
      inc(fAssertionsFailed,C.fAssertionsFailed);
    end;
  except
    on E: Exception do begin
      // assume any exception not intercepted above is a failure
      Color(ccLightRed);
      fFailed.AddObject(E.ClassName+': '+E.Message,C);
      writeln(fSaveToFile,#13#10'! Exception ',E.ClassName,
        ' raised with messsage:'#13#10'!  ',E.Message);
    end;
  end;
  Color(ccLightCyan);
  result := (fFailed.Count=0);
{$ifdef MSWINDOWS}
  Elapsed := #13#10#13#10'Time elapsed for all tests: '+RunTimer.Stop;
  if Exeversion.Version.Major<>0 then
    Version := #13#10'Software version tested: '+RawUTF8(Exeversion.Version.Detailed);
{$endif}
  Writeln(fSaveToFile,#13#10,Version,CustomVersions,
    #13#10'Generated with: ',GetDelphiCompilerVersion,' compiler', Elapsed,
    #13#10'Tests performed at ',DateTimeToStr(Now));
  if result then
    Color(ccWhite) else
    Color(ccLightRed);
  write(fSaveToFile,#13#10'Total assertions failed for all test suits:  ',
    IntToThousandString(AssertionsFailed),' / ', IntToThousandString(Assertions));
  if result then begin
    Color(ccLightGreen);
    Writeln(fSaveToFile,#13#10'! All tests passed successfully.');
  end else
    Writeln(fSaveToFile,#13#10'! Some tests FAILED: please correct the code.');
  Color(ccLightGray);
end;

procedure TSynTests.SaveToFile(const DestPath, FileName: TFileName);
var FN: TFileName;
begin
  if TTextRec(fSaveToFile).Handle<>0 then
    Close(fSaveToFile);
  if FileName='' then
    FN := DestPath+Ident+'.txt' else
    FN := DestPath+FileName;
  assign(fSaveToFile,FN);
  rewrite(fSaveToFile);
  if IOResult<>0 then
    fillchar(fSaveToFile,sizeof(fSaveToFile),0);
end;

{$I+}

{ TSynCache }

procedure TSynCache.Add(const aValue: RawUTF8; aTag: PtrInt);
begin
  if (self=nil) or (fFindLastAddedIndex<0) then
    // fFindLastAddedIndex should have been set by a previous call to Find() 
    exit;
................................................................................
      if ValuesCount=0 then
        break;
    end;
    fStream.Write(pointer(fBuf)^,fPos);
    fPos := 0;
  until false;
end;






















{ TFileBufferReader }

procedure TFileBufferReader.Close;
begin
  fMap.UnMap;
................................................................................
  CheckVTableInitialized;
  Result := VTable.Validate(Pointer(VValue),RecordIndex);
end;

{$endif DELPHI5OROLDER}


{ TSynMapFile }

const
  MAGIC_MAB = $A5A5A5A5;

type
  TSynLZHead = packed record
    Magic: cardinal;
    CompressedSize: integer;
    HashCompressed: cardinal;
    UnCompressedSize: integer;
    HashUncompressed: cardinal;
................................................................................
  end;
  PSynLZHead = ^TSynLZHead;
  TSynLZTrailer = packed record
    HeaderRelativeOffset: cardinal;
    Magic: cardinal;
  end;
  PSynLZTrailer = ^TSynLZTrailer;












function StreamSynLZ(Source: TCustomMemoryStream; Dest: TStream; Magic: cardinal): integer;
var DataLen: integer;
    S: pointer;
    P: pointer;
    Head: TSynLZHead;
    Trailer: TSynLZTrailer;
................................................................................
       {$endif}
       (crc32c(0,pointer(result),len)<>PCardinal(P)^)) then
      result := '';
  end;
  end;
end;

function MatchPattern(P,PEnd,Up: PUTF8Char; var Dest: PUTF8Char): boolean;
begin
  result := false;
  repeat
    if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
    while NormToUpperAnsi7[P^]=Up^ do begin
      inc(P);
      if P>PEnd then exit;
      inc(Up);
      if (Up^=' ') and (P^ in [#1..' ']) then begin // ignore multiple spaces in P^
        while (P<PEnd) and (P^ in [#1..' ']) do inc(P);
        inc(Up);
      end;
    end;
    if Up^=#0 then // all chars of Up^ found in P^
      break else
    if Up^<>' ' then // P^ and Up^ didn't match
      exit;
    inc(Up);
  until false;
  while (P<PEnd) and (P^=' ') do inc(P); // ignore all spaces
  result := true;
  Dest := P;
end;

procedure ReadSymbol(var R: TFileBufferReader; var A: TDynArray);
var i, n, L: integer;
    S: PSynMapSymbol;
    Addr: cardinal;
    P: PByte;
begin
  n := R.ReadVarUInt32;
  A.Count := n;
  P := R.CurrentMemory;
  if (n=0) or (P=nil) then
    exit;
  S := A.Value^;
  for i := 0 to n-1 do begin
    L := FromVarUInt32(P); // inlined R.Read(S^.Name)
    SetString(S^.Name,PAnsiChar(P),L);
    inc(P,L);
    inc(PtrUInt(S),A.ElemSize);
  end;
  S := A.Value^;
  Addr := FromVarUInt32(P);
  S^.Start := Addr;
  for i := 1 to n-1 do begin
    inc(Addr,FromVarUInt32(P));
    S^.Stop := Addr-1;
    inc(PtrUInt(S),A.ElemSize);
    S^.Start := Addr;
  end;
  S^.Stop := Addr+FromVarUInt32(P);
  R.fCurrentPos := PtrUInt(P)-PtrUInt(R.fMap.fBuf);
end;

const
  /// Delphi linker starts the code section at this fixed offset
  CODE_SECTION = $1000;

var
  GlobalCriticalSection: TRTLCriticalSection;
  GlobalCriticalSectionInitialized: boolean;

procedure GlobalLock;
begin
  if not GlobalCriticalSectionInitialized then begin
    InitializeCriticalSection(GlobalCriticalSection);
    GlobalCriticalSectionInitialized := true;
  end;
  EnterCriticalSection(GlobalCriticalSection);
end;

procedure GlobalUnLock;
begin
  if GlobalCriticalSectionInitialized then
    LeaveCriticalSection(GlobalCriticalSection);
end;


constructor TSynMapFile.Create(const aExeName: TFileName=''; MabCreate: boolean=true);

  procedure LoadMap;
    var P, PEnd: PUTF8Char;
    procedure NextLine;
    begin
      while (P<PEnd) and (P^>=' ') do inc(P);
      if (P<PEnd) and (P^=#13) then inc(P);
      if (P<PEnd) and (P^=#10) then inc(P);
    end;
    function GetCode(var Ptr: cardinal): boolean;
    begin
      while (P<PEnd) and (P^=' ') do inc(P);
      result := false;
      if (P+10<PEnd) and
         (PInteger(P)^=ord('0')+ord('0')shl 8+ord('0')shl 16+ord('1')shl 24) and
         (P[4]=':') then begin
        if not HexDisplayToBin(PAnsiChar(P)+5,@Ptr,sizeof(Ptr)) then exit;
        while (P<PEnd) and (P^>' ') do inc(P);
        while (P<PEnd) and (P^=' ') do inc(P);
        if P<PEnd then
          result := true;
      end;
    end;
    procedure ReadSegments;
    var Beg: PAnsiChar;
        U: TSynMapUnit;
    begin
      NextLine;
      NextLine;
      while (P<PEnd) and (P^<' ') do inc(P);
      while (P+10<PEnd) and (P^>=' ') do begin
        if GetCode(U.Symbol.Start) and
           HexDisplayToBin(PAnsiChar(P),@U.Symbol.Stop,4) then begin
          while PWord(P)^<>ord('M')+ord('=')shl 8 do
            if P+10>PEnd then exit else inc(P);
          Beg := pointer(P+2);
          while (P<PEnd) and (P^>' ') do inc(P);
          SetString(U.Symbol.Name,Beg,P-Beg);
          inc(U.Symbol.Stop,U.Symbol.Start-1);
          if (U.Symbol.Name<>'') and
             ((U.Symbol.Start<>0) or (U.Symbol.Stop<>0)) then
            fUnits.FindHashedAndUpdate(U,true); // true for adding
        end;
        NextLine;
      end;
    end;
    procedure ReadSymbols;
    var Beg: PAnsiChar;
        Sym: TSynMapSymbol;
    begin
      NextLine;
      NextLine;
      while (P+10<PEnd) and (P^>=' ') do begin
        if GetCode(Sym.Start) then begin
          while (P<PEnd) and (P^=' ') do inc(P);
          Beg := pointer(P);
          {$ifdef HASINLINE}
          // trim left 'UnitName.' for each symbol (since Delphi 2005)
          case PWord(P)^ of
          ord('S')+ord('y') shl 8:
             if IdemPChar(P+2,'STEM.') then
               if IdemPChar(P+7,'WIN.') then inc(P,9) else
               if IdemPChar(P+7,'RTTI.') then inc(P,10) else
               if IdemPChar(P+7,'TYPES.') then inc(P,10) else
               if IdemPChar(P+7,'ZLIB.') then inc(P,10) else
               if IdemPChar(P+7,'CLASSES.') then inc(P,10) else
               if IdemPChar(P+7,'SYSUTILS.') then inc(P,10) else
               if IdemPChar(P+7,'VARUTILS.') then inc(P,10) else
               if IdemPChar(P+7,'STRUTILS.') then inc(P,10) else
               if IdemPChar(P+7,'SYNCOBJS.') then inc(P,10) else
               if IdemPChar(P+7,'GENERICS.') then inc(P,16) else
               if IdemPChar(P+7,'CHARACTER.') then inc(P,10) else
               if IdemPChar(P+7,'TYPINFO.') then inc(P,10) else
               if IdemPChar(P+7,'VARIANTS.') then inc(P,10);
          ord('W')+ord('i') shl 8: if IdemPChar(P+2,'NAPI.') then inc(P,7);
          ord('V')+ord('c') shl 8: if IdemPChar(P+2,'L.') then inc(P,7);
          end;
          while (P<PEnd) and (P^<>'.') do if P^<=' ' then break else inc(P);
          if P^='.' then begin
            while (P<PEnd) and (P^='.') do inc(P);
            Beg := pointer(P);
          end else
            P := pointer(Beg); // no '.' found
          {$endif}
          while (P<PEnd) and (P^>' ') do inc(P);
          SetString(Sym.Name,Beg,P-Beg);
          if (Sym.Name<>'') and not (Sym.Name[1] in ['$','?']) then
            fSymbols.Add(Sym);
        end;
        NextLine;
      end;
    end;
    procedure ReadLines;
    var Beg: PAnsiChar;
        i, Count, n: integer;
        aName: RawUTF8;
        added: boolean;
        U: ^TSynMapUnit;
    begin
      Beg := pointer(P);
      while P^<>'(' do if P=PEnd then exit else inc(P);
      SetString(aName,Beg,P-Beg);
      if aName='' then
        exit;
      i := fUnits.FindHashedForAdding(aName,added);
      U := @fUnit[i];
      if added then
        U^.Symbol.Name := aName; // should not occur, but who knows...
      if U^.FileName='' then begin
        inc(P); Beg := pointer(P);
        while P^<>')' do if P=PEnd then exit else inc(P);
        SetString(U^.FileName,Beg,P-Beg);
      end;
      NextLine;
      NextLine;
      n := length(U^.Line);
      Count := n; // same unit may appear multiple times in .map content
      while (P+10<PEnd) and (P^>=' ') do begin
        while (P<PEnd) and (P^=' ') do inc(P);
        repeat
          if Count=n then begin
            n := Count+256;
            SetLength(U^.Line,n);
            SetLength(U^.Addr,n);
          end;
          U^.Line[Count] := GetNextItemCardinal(P,' ');
          if not GetCode(cardinal(U^.Addr[Count])) then
            break;
          if U^.Addr[Count]<>0 then
            inc(Count); // occured with Delphi 2010 :(
        until (P>=PEnd) or (P^<' ');
        NextLine;
      end;
      SetLength(U^.Line,Count);
      SetLength(U^.Addr,Count);
    end;
  var i, s,u: integer;
      RehashNeeded: boolean;
  begin // LoadMap
    fSymbols.Capacity := 8000;
    with TSynMemoryStreamMapped.Create(fMapFile) do
    try
      // parse .map sections into fSymbol[] and fUnit[]
      P := Memory;
      PEnd := P+Size;
      while P<PEnd do
        if MatchPattern(P,PEnd,'DETAILED MAP OF SEGMENTS',P) then
          ReadSegments else
        if MatchPattern(P,PEnd,'ADDRESS PUBLICS BY VALUE',P) then
          ReadSymbols else
        if MatchPattern(P,PEnd,'LINE NUMBERS FOR',P) then
          ReadLines else
         NextLine;
      // now we should have read all .map content
      s := fSymbols.Count-1;
      RehashNeeded := false;
      for i := fUnits.Count-1 downto 0 do
        with fUnit[i] do
          if (Symbol.Start=0) and (Symbol.Stop=0) then begin
            fUnits.Delete(i); // occurs with Delphi 2010 :(
            RehashNeeded := true;
          end;
      u := fUnits.Count-1;
      if RehashNeeded then
        fUnits.ReHash; // as expected by TDynArrayHashed
      {$ifopt C+}
      for i := 1 to u do
         assert(fUnit[i].Symbol.Start>fUnit[i-1].Symbol.Stop);
      {$endif}
      for i := 0 to s-1 do
        fSymbol[i].Stop := fSymbol[i+1].Start-1;
      if (u>=0) and (s>=0) then
        fSymbol[s].Stop := fUnit[u].Symbol.Stop;
    finally
      Free;
    end;
  end;

  procedure LoadMab(const aMabFile: TFileName);
  var R: TFileBufferReader;
      i: integer;
      S: TCustomMemoryStream;
      MS: TMemoryStream;
  begin
    fMapFile := aMabFile;
    if FileExists(aMabfile) then
    try
      S := TSynMemoryStreamMapped.Create(aMabFile);
      try
        MS := StreamUnSynLZ(S,MAGIC_MAB);
        if MS<>nil then
        try
          R.OpenFrom(MS.Memory,MS.Size);
          ReadSymbol(R,fSymbols);
          ReadSymbol(R,fUnits{$ifdef UNDIRECTDYNARRAY}.InternalDynArray{$endif});
          fUnits.ReHash;
          for i := 0 to fUnits.Count-1 do
          with fUnit[i] do begin
            R.Read(FileName);
            R.ReadVarUInt32Array(Line);
            R.ReadVarUInt32Array(Addr);
          end;
          MabCreate := false;
        finally
          MS.Free;
        end;
      finally
        S.Free;
      end;
    except
      on Exception do; // invalid file -> ignore any problem
    end;
  end;

var SymCount, UnitCount, i: integer;
    MabFile: TFileName;
    MapAge, MabAge: TDateTime;
begin
  fSymbols.Init(TypeInfo(TSynMapSymbolDynArray),fSymbol,@SymCount);
  fUnits.Init(TypeInfo(TSynMapUnitDynArray),fUnit,nil,nil,nil,@UnitCount);
  // 1. search for an external .map file matching the running .exe/.dll name
  if aExeName='' then begin
    fMapFile := GetModuleName(hInstance);
    {$ifdef MSWINDOWS}
    fGetModuleHandle := GetModuleHandle(pointer(
    {$else}
    fGetModuleHandle := LoadLibrary(PChar(
    {$endif}
      ExtractFileName(fMapFile)))+CODE_SECTION;
  end else
    fMapFile := aExeName;
  fMapFile := ChangeFileExt(fMapFile,'.map');
  MabFile := ChangeFileExt(fMapFile,'.mab');
  GlobalLock;
  try
    MapAge := FileAgeToDateTime(fMapFile);
    MabAge := FileAgeToDateTime(MabFile);
    if (MabAge<=MapAge) and (MapAge>0) then
      LoadMap; // if no faster-to-load .mab available and accurate
    // 2. search for a .mab file matching the running .exe/.dll name
    if (SymCount=0) and (MabAge<>0) then
      LoadMab(MabFile);
    // 3. search for an embedded compressed .mab file appended to the .exe/.dll
    if SymCount=0 then
      LoadMab(GetModuleName(hInstance));
    // finalize symbols
    if SymCount>0 then begin
      for i := 1 to SymCount-1 do
        assert(fSymbol[i].Start>fSymbol[i-1].Stop);
      SetLength(fSymbol,SymCount);
      SetLength(fUnit,UnitCount);
      fSymbols.fCountP := nil;
      fUnits.{$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$endif}fCountP := nil;
      if MabCreate then
        SaveToFile(MabFile); // if just created from .map -> create .mab file
      fHasDebugInfo := true;
    end else
      fMapFile := '';
  finally
    GlobalUnLock;
  end;
end;

procedure WriteSymbol(var W: TFileBufferWriter; const A: TDynArray);
var i, n: integer;
    Diff: cardinal;
    S: PSynMapSymbol;
    P: PByte;
    Beg: PtrUInt;
begin
  n := A.Count;
  W.WriteVarUInt32(n);
  if n=0 then exit;
  S := A.Value^;
  for i := 0 to n-1 do begin
    W.Write(S^.Name);
    inc(PtrUInt(S),A.ElemSize);
  end;
  S := A.Value^;
  Diff := S^.Start;
  W.WriteVarUInt32(Diff);
  if W.fPos+n*5>W.fBufLen then
    W.fTotalWritten := W.Flush;
  with W do
    if fPos+n*5>fBufLen then // BufLen=1 shl 19=512 KB should be enough
      raise ESynException.CreateUTF8('TSynMapFile.WriteSymbol: too big %',[
        PShortString(@PDynArrayTypeInfo(A.ArrayType).NameLen)^]) else
      P := @PByteArray(fBuf)^[fPos];
  Beg := PtrUInt(P);
  for i := 1 to n-1 do begin
    inc(PtrUInt(S),A.ElemSize);
    P := ToVarUInt32(S^.Start-Diff,P);
    Diff := S^.Start;
  end;
  P := ToVarUInt32(S^.Stop-Diff,P);
  Beg := PtrUInt(P)-Beg;
  inc(W.fPos,Beg);
  inc(W.fTotalWritten,Beg);
end;

procedure TSynMapFile.SaveToStream(aStream: TStream);
var W: TFileBufferWriter;
    i: integer;
    MS: TMemoryStream;
begin
  MS := THeapMemoryStream.Create;
  W := TFileBufferWriter.Create(MS,1 shl 20); // 1 MB should be enough
  try
    WriteSymbol(W,fSymbols);
    WriteSymbol(W,fUnits{$ifdef UNDIRECTDYNARRAY}.InternalDynArray{$endif});
    for i := 0 to high(fUnit) do
    with fUnit[i] do begin
      W.Write(FileName);
      W.WriteVarUInt32Array(Line,length(Line),wkOffsetI); // not always increasing
      W.WriteVarUInt32Array(Addr,length(Addr),wkOffsetU); // always increasing
    end;
    W.Flush;
    StreamSynLZ(MS,aStream,MAGIC_MAB);
  finally
    MS.Free;
    W.Free;
  end;
end;

function TSynMapFile.SaveToFile(const aFileName: TFileName=''): TFileName;
var F: TFileStream;
begin
  if aFileName='' then
    result := ChangeFileExt(GetModuleName(hInstance),'.mab') else
    result := aFileName;
  DeleteFile(result);
  F := TFileStream.Create(result,fmCreate);
  try
    SaveToStream(F);
  finally
    F.Free;
  end;
end;

procedure TSynMapFile.SaveToExe(const aExeName: TFileName);
var FN: TFileName;
    MS, MAB: TMemoryStream;
    Len, LenMAB: PtrUInt;
    P: PAnsiChar;
begin
  if not FileExists(aExeName) then
    exit;
  FN := SaveToFile(ChangeFileExt(aExeName,'.mab'));
  try
    MS := THeapMemoryStream.Create;
    MAB := THeapMemoryStream.Create;
    try
      // load both files
      MAB.LoadFromFile(FN);
      LenMAB := MAB.Size;
      MS.LoadFromFile(aExeName);
      Len := MS.Size;
      if Len<16 then
        exit;
      P := MS.Memory;
      inc(P,Len);
      with PSynLZTrailer(P-sizeof(TSynLZTrailer))^ do
        if (Magic=MAGIC_MAB) and (HeaderRelativeOffset<Len) and
           (PSynLZHead(P-HeaderRelativeOffset)^.Magic=MAGIC_MAB) then
          // trim existing mab content
          dec(Len,HeaderRelativeOffset);
      // append mab content to exe
      MS.Size := Len+LenMAB;
      move(MAB.Memory^,PAnsiChar(MS.Memory)[Len],LenMAB);
      MS.SaveToFile(aExeName);
    finally
      MAB.Free;
      MS.Free;
    end;
  finally
    DeleteFile(FN);
  end;
end;

function TSynMapFile.FindSymbol(aAddr: cardinal): integer;
var L,R: integer;
begin
  R := high(fSymbol);
  L := 0;
  if (R>=0) and (aAddr>=fSymbol[0].Start) and (aAddr<=fSymbol[R].Stop) then
  repeat
    result := (L+R)shr 1;
    with fSymbol[result] do
      if aAddr<Start then
        R := result-1 else
      if aAddr>Stop then
        L := result+1 else
        exit;
  until L>R;
  result := -1;
end;

function TSynMapFile.FindUnit(aAddr: cardinal; out LineNumber: integer): integer;
var L,R,n,max: integer;
begin
  LineNumber := 0;
  R := high(fUnit);
  L := 0;
  if (R>=0) and
     (aAddr>=fUnit[0].Symbol.Start) and (aAddr<=fUnit[R].Symbol.Stop) then
  repeat
    result := (L+R) shr 1;
    with fUnit[result] do
      if aAddr<Symbol.Start then
        R := result-1 else
      if aAddr>Symbol.Stop then
        L := result+1 else begin
        // unit found -> search line number
        L := 0;
        max := high(Addr);
        R := max;
        if R>=0 then
        repeat
          n := (L+R) shr 1;
          if aAddr<cardinal(Addr[n]) then
            R := n-1 else
          if (n<max) and (aAddr>=cardinal(Addr[n+1])) then
            L := n+1 else begin
            LineNumber := Line[n];
            exit;
          end;
        until L>R;
        exit;
      end;
  until L>R;
  result := -1;
end;

var
  InstanceMapFile: TSynMapFile;
  
class procedure TSynMapFile.Log(W: TTextWriter; Addr: PtrUInt);
var u, s, Line: integer;
begin
  if (W=nil) or (Addr=0) or (InstanceMapFile=nil) then
    exit;
  with InstanceMapFile do
  if HasDebugInfo then begin
    dec(Addr,fGetModuleHandle);
    s := FindSymbol(Addr);
    u := FindUnit(Addr,Line);
    if s<0 then begin
      if u<0 then
        exit;
    end else
      with PInt64Rec(Symbols[s].Name)^ do
      if Lo=ord('T')+ord('S')shl 8+ord('y')shl 16+ord('n')shl 24 then
      case Hi of
        ord('L')+ord('o')shl 8+ord('g')shl 16+ord('.')shl 24,
        ord('T')+ord('e')shl 8+ord('s')shl 16+ord('t')shl 24:
          exit; // don't log stack trace internal to TSynLog.*/TSynTest* methods
      end;
    W.AddPointer(Addr); // only display addresses inside known Delphi code
    W.Add(' ');
    if u>=0 then begin
      W.AddString(Units[u].Symbol.Name);
      if s>=0 then
        if Symbols[s].Name=Units[u].Symbol.Name then
          s := -1 else
          W.Add('.');
    end;
    if s>=0 then
      W.AddString(Symbols[s].Name);
    W.Add(' ');
    if Line>0 then begin
      W.Add('(');
      W.Add(Line);
      W.Add(')',' ');
    end;
  end else begin
    W.AddPointer(Addr); // no .map info available -> display all addresses 
    W.Add(' ');
  end;
end;

function TSynMapFile.FindLocation(aAddr: Cardinal): RawUTF8;
var u,s,Line: integer;
begin
  result := '';
  if (aAddr=0) or not HasDebugInfo then
    exit;
  dec(aAddr,fGetModuleHandle);
  s := FindSymbol(aAddr);
  u := FindUnit(aAddr,Line);
  if (s<0) and (u<0) then
    exit;
  if u>=0 then begin
    with Units[u] do begin
      if FileName<>'' then
        result := FileName+' - ';
      result := result+Symbol.Name;
    end;
    if s>=0 then
      if Symbols[s].Name=Units[u].Symbol.Name then
        s := -1 else
        result := result+'.';
  end;
  if s>=0 then
    result := result+Symbols[s].Name;
  if Line>0 then
    result := result+' ('+Int32ToUtf8(Line)+')';
end;


{ TSynLogFamily }

type
  /// an array to all available per-thread TSynLogFile instances
  TSynLogFileIndex = array[0..MAX_SYNLOGFAMILY] of integer;

var
  /// internal list of registered TSynLogFamily
  // - up to MAX_SYNLOGFAMILY+1 families may be defined
  SynLogFamily: TObjectList = nil;

  /// internal list of created TSynLog instance, one per each log file on disk
  // - do not use directly - necessary for inlining TSynLogFamily.SynLog method
  // - also used by AutoFlushProc() to get a global list of TSynLog instances
  SynLogFileList: TObjectListLocked = nil;

threadvar
  /// each thread can access to its own TSynLogFile
  // - is used to implement TSynLogFamily.PerThreadLog=ptOneFilePerThread option
  // - the current TSynLogFile instance of the living thread is
  // ! SynLogFileList[SynLogFileIndexThreadVar[TSynLogFamily.Ident]-1]
  SynLogFileIndexThreadVar: TSynLogFileIndex;


{ ESynException }

constructor ESynException.CreateUTF8(Format: PUTF8Char; const Args: array of const);
begin
  Create(UTF8ToString(FormatUTF8(Format,Args)));
end;

{$ifndef NOEXCEPTIONINTERCEPT}

function ESynException.CustomLog(WR: TTextWriter; const Context: TSynLogExceptionContext): boolean;

begin
  if Assigned(TSynLogExceptionToStrCustom) then
    result := TSynLogExceptionToStrCustom(WR,Context) else
    result := DefaultSynLogExceptionToStr(WR,Context);
end;

/// if defined, will use AddVectoredExceptionHandler() API call
// - this one does not produce accurate stack trace by now, and is supported
// only since Windows XP
// - so default method using RTLUnwindProc should be prefered
{.$define WITH_VECTOREXCEPT}

{$ifdef CPU64}
  {$define WITH_VECTOREXCEPT}
{$endif}

{$ifdef DELPHI5OROLDER}
// Delphi 5 doesn't define the needed RTLUnwindProc variable :(
// so we will patch the System.pas RTL in-place

var
  RTLUnwindProc: Pointer;

procedure PatchCallRtlUnWind;
procedure Patch(P: PAnsiChar);
{   004038B6 52               push edx  // Save exception object
    004038B7 51               push ecx  // Save exception address
    004038B8 8B542428         mov edx,[esp+$28]
    004038BC 83480402         or dword ptr [eax+$04],$02
    004038C0 56               push esi  // Save handler entry
    004038C1 6A00             push $00
    004038C3 50               push eax
    004038C4 68CF384000       push $004038cf  // @@returnAddress
    004038C9 52               push edx
    004038CA E88DD8FFFF       call RtlUnwind
    ...
RtlUnwind:
    0040115C FF255CC14100     jmp dword ptr [$0041c15c]
    where $0041c15c is a pointer to the address of RtlUnWind in kernel32.dll
    -> we will replace [$0041c15c] by [RTLUnwindProc]    }
var i: Integer;
    addr: PAnsiChar;
begin
  for i := 0 to 31 do
    if (PCardinal(P)^=$6850006a) and  // push 0; push eax; push @@returnAddress
       (PWord(P+8)^=$E852) then begin // push edx; call RtlUnwind
      inc(P,10); // go to call RtlUnwind address
      if PInteger(P)^<0 then begin
        addr := P+4+PInteger(P)^;
        if PWord(addr)^=$25FF then begin // jmp dword ptr []
          PatchCodePtrUInt(Pointer(addr+2),cardinal(@RTLUnwindProc));
          exit;
        end;
      end;
    end else
    inc(P);
end;
asm
  mov eax,offset System.@HandleAnyException+200
  call Patch
end;
{$endif DELPHI5OROLDER}

var
  /// used internaly by function GetHandleExceptionSynLog
  CurrentHandleExceptionSynLog: TSynLog;

function GetHandleExceptionSynLog: TSynLog;
var Index: ^TSynLogFileIndex;
    i: integer;
    ndx, n: cardinal;
begin
  result := CurrentHandleExceptionSynLog;
  if (result<>nil) and result.fFamily.fHandleExceptions then
    exit;
  if SynLogFileList=nil then begin
    // we are here if no log content was generated yet (i.e. no log file yet)
    for i := 0 to SynLogFamily.Count-1 do
      with TSynLogFamily(SynLogFamily.List[i]) do
      if fHandleExceptions then begin
        result := SynLog;
        exit;
      end;
  end else begin
    SynLogFileList.Lock;
    try
      Index := @SynLogFileIndexThreadVar;
      n := SynLogFileList.Count;
      for i := 0 to high(Index^) do begin
        ndx := Index^[i]-1;
        if ndx<=n then begin
          result := TSynLog(SynLogFileList.List[ndx]);
          if result.fFamily.fHandleExceptions then
            exit;
        end;
      end;
      for i := 0 to n-1 do begin
        result := TSynLog(SynLogFileList.List[i]);
        if result.fFamily.fHandleExceptions then
          exit;
      end;
    finally
      SynLogFileList.UnLock;
    end;
  end;
  result := nil;
end;

type
  PExceptionRecord = ^TExceptionRecord;
  TExceptionRecord = record
    ExceptionCode: DWord;
    ExceptionFlags: DWord;
    OuterException: PExceptionRecord;
    ExceptionAddress: PtrUInt;
    NumberParameters: Longint;
    case {IsOsException:} Boolean of
    True:  (ExceptionInformation : array [0..14] of PtrUInt);
    False: (ExceptAddr: PtrUInt; ExceptObject: Exception);
  end;
  GetExceptionClass = function(const P: TExceptionRecord): ExceptClass;

const
  cDelphiExcept = $0EEDFAE0;
  cDelphiException = $0EEDFADE;
  // see http://msdn.microsoft.com/en-us/library/xcb2z8hs 
  cSetThreadNameException = $406D1388;

  DOTNET_EXCEPTIONNAME: array[0..83] of RawUTF8 = (
  'Access', 'AmbiguousMatch', 'appdomainUnloaded', 'Application', 'Argument',
  'ArgumentNull', 'ArgumentOutOfRange', 'Arithmetic', 'ArrayTypeMismatch',
  'BadImageFormat', 'CannotUnloadappdomain', 'ContextMarshal', 'Cryptographic',
  'CryptographicUnexpectedOperation', 'CustomAttributeFormat', 'DirectoryNotFound',
  'DirectoryNotFound', 'DivideByZero', 'DllNotFound', 'DuplicateWaitObject',
  'EndOfStream', 'EntryPointNotFound', '', 'ExecutionEngine', 'External',
  'FieldAccess', 'FileLoad', 'FileLoad', 'FileNotFound', 'Format',
  'IndexOutOfRange', 'InvalidCast', 'InvalidComObject', 'InvalidFilterCriteria',
  'InvalidOleVariantType', 'InvalidOperation', 'InvalidProgram', 'IO',
  'IsolatedStorage', 'MarshalDirective', 'MethodAccess', 'MissingField',
  'MissingManifestResource', 'MissingMember', 'MissingMethod',
  'MulticastNotSupported', 'NotFiniteNumber', 'NotImplemented', 'NotSupported',
  'NullReference', 'OutOfMemory', 'Overflow', 'PlatformNotSupported', 'Policy',
  'Rank', 'ReflectionTypeLoad', 'Remoting', 'RemotingTimeout', 'SafeArrayTypeMismatch',
  'SafeArrayRankMismatch', 'Security', 'SEH', 'Serialization', 'Server', 'StackOverflow',
  'SUDSGenerator', 'SUDSParser', 'SynchronizationLock', 'System', 'Target',
  'TargetInvocation', 'TargetParameterCount', 'ThreadAbort', 'ThreadInterrupted',
  'ThreadState', 'ThreadStop', 'TypeInitialization', 'TypeLoad', 'TypeUnloaded',
  'UnauthorizedAccess', 'InClassConstructor', 'KeyNotFound', 'InsufficientStack',
  'InsufficientMemory');
  // http://blogs.msdn.com/b/yizhang/archive/2010/12/17/interpreting-hresults-returned-from-net-clr-0x8013xxxx.aspx
  DOTNET_EXCEPTIONHRESULT: array[0..83] of cardinal = (
   $8013151A, $8000211D, $80131015, $80131600, $80070057, $80004003, $80131502,
   $80070216, $80131503, $8007000B, $80131015, $80090020, $80004001, $80131431,
   $80131537, $80070003, $80030003, $80020012, $80131524, $80131529, $801338,
   $80131522, $80131500, $80131506, $80004005, $80131507, $80131621, $80131018,
   $80070002, $80131537, $80131508, $80004002, $80131527, $80131601, $80131531,
   $80131509, $8013153A, $80131620, $80131450, $80131535, $80131510, $80131511,
   $80131532, $80131512, $80131513, $80131514, $80131528, $80004001, $80131515,
   $80004003, $8007000E, $80131516, $80131539, $80131416, $80131517,
   $80131602, $8013150B, $8013150B, $80131533, $80131538, $8013150A, $80004005,
   $8013150C, $8013150E, $800703E9, $80131500, $80131500, $80131518, $80131501,
   $80131603, $80131604, $80138002, $80131530, $80131519, $80131520, $80131521,
   $80131534, $80131522, $80131013, $80070005, $80131543, $80131577, $80131578,
   $8013153D);

type
  // avoid linking of ComObj.pas just for EOleSysError
  EOleSysError = class(Exception)
  public
    ErrorCode: cardinal;
  end;

function ExceptionInheritsFrom(E: TClass; const Name: ShortString): boolean;
begin // avoid linking of ComObj.pas just for EOleSysError
  while (E<>nil) and (E<>Exception) do
    if IdemPropName(PShortString(PPointer(PtrInt(E)+vmtClassName)^)^,Name) then begin
      result := true;
      exit;
    end else begin
      E := PPointer(PtrInt(E)+vmtParent)^;
      if E<>nil then
        E := PPointer(E)^;
    end;
  result := false;
end;

function DefaultSynLogExceptionToStr(WR: TTextWriter; const Context: TSynLogExceptionContext): boolean;
var i: integer;
begin
  WR.AddClassName(Context.EClass);
  if (Context.Level=sllException) and (Context.EInstance<>nil) and
     (Context.EClass<>EExternalException) then begin
    if ExceptionInheritsFrom(Context.EClass,'EOleSysError') then begin
      WR.Add(' ');
      WR.AddPointer(EOleSysError(Context.EInstance).ErrorCode);
      for i := 0 to high(DOTNET_EXCEPTIONHRESULT) do
        if DOTNET_EXCEPTIONHRESULT[i]=EOleSysError(Context.EInstance).ErrorCode then begin
          WR.AddShort(' [.NET/CLR unhandled ');
          WR.AddString(DOTNET_EXCEPTIONNAME[i]);
          WR.AddShort('Exception]');
        end; // no break on purpose, if ErrorCode matches more than one Exception
    end;
    WR.AddShort(' ("');
    WR.AddJSONEscapeString(Context.EInstance.Message);
    WR.AddShort('")');
  end else begin
    WR.AddShort(' (');
    WR.AddPointer(Context.ECode);
    WR.AddShort(')');
  end;
  result := false; // caller should append "at EAddr" and the stack trace
end;

procedure LogExcept(stack: PPtrUInt; const Exc: TExceptionRecord);
var SynLog: TSynLog;
    Ctxt: TSynLogExceptionContext;
    LastError: DWORD;
begin
  if Exc.ExceptionCode=cSetThreadNameException then
    exit;
  SynLog := GetHandleExceptionSynLog;
  if SynLog=nil then
    exit;
  LastError := GetLastError;
  if (Exc.ExceptionCode=cDelphiException) and (Exc.ExceptObject<>nil) then begin
    if Exc.ExceptObject.InheritsFrom(Exception) then
      Ctxt.EClass := PPointer(Exc.ExceptObject)^ else
      Ctxt.EClass := EExternalException;
    if sllException in SynLog.fFamily.Level then
      Ctxt.Level := sllException else
      Ctxt.Level := sllError;
    Ctxt.EAddr := Exc.ExceptAddr;
  end else begin
    {$ifdef MSWINDOWS}
    if Assigned(ExceptClsProc) then
      Ctxt.EClass := GetExceptionClass(ExceptClsProc)(Exc) else
    {$endif}
      Ctxt.EClass := EExternal;
    Ctxt.Level := sllExceptionOS;
    Ctxt.EAddr := Exc.ExceptionAddress;
  end;
  if (Ctxt.Level<>sllError) and
     (SynLog.fFamily.ExceptionIgnore.IndexOf(Ctxt.EClass)<0) then begin
    if SynLog.LogHeaderLock(Ctxt.Level) then
    try
      Ctxt.EInstance := Exc.ExceptObject;
      Ctxt.ECode := Exc.ExceptionCode;
      repeat
        if (Ctxt.Level=sllException) and (Exc.ExceptionCode=cDelphiException) and
           Ctxt.EInstance.InheritsFrom(ESynException) then begin
          if ESynException(Ctxt.EInstance).CustomLog(SynLog.fWriter,Ctxt) then
            break;
        end else
        if Assigned(TSynLogExceptionToStrCustom) then begin
          if TSynLogExceptionToStrCustom(SynLog.fWriter,Ctxt) then
            break;
        end else
          if DefaultSynLogExceptionToStr(SynLog.fWriter,Ctxt) then
            break;
        SynLog.fWriter.AddShort(' at ');
        TSynMapFile.Log(SynLog.fWriter,Ctxt.EAddr);
  {$ifndef WITH_VECTOREXCEPT} // stack frame is only correct for RTLUnwindProc
        SynLog.AddStackTrace(stack);
  {$endif}
        break;
      until false;
      SynLog.fWriter.AddEndOfLine(SynLog.fCurrentLevel);
      SynLog.fWriter.Flush; // we expect exceptions to be available on disk
    finally
      LeaveCriticalSection(SynLog.fThreadLock);
    end;
  end;
  SetLastError(LastError); // code above could have changed this
end;


{$ifdef WITH_VECTOREXCEPT}
type
  PExceptionInfo = ^TExceptionInfo;
  TExceptionInfo = packed record
    ExceptionRecord: PExceptionRecord;
    ContextRecord: pointer;
  end;

var
  AddVectoredExceptionHandler: function(FirstHandler: cardinal;
    VectoredHandler: pointer): PtrInt; stdcall;

function SynLogVectoredHandler(ExceptionInfo : PExceptionInfo): PtrInt; stdcall;
const
  EXCEPTION_CONTINUE_SEARCH = 0;
begin
  if CurrentHandleExceptionSynLog<>nil then
    LogExcept(nil,ExceptionInfo^.ExceptionRecord^);
  result := EXCEPTION_CONTINUE_SEARCH;
end;

{$else WITH_VECTOREXCEPT}

{$ifdef DELPHI5OROLDER}
procedure RtlUnwind; external kernel32 name 'RtlUnwind';
{$else}
var
  oldUnWindProc: pointer;
{$endif DELPHI5OROLDER}

procedure SynRtlUnwind(TargetFrame, TargetIp: pointer;
  ExceptionRecord: PExceptionRecord; ReturnValue: Pointer); stdcall;
asm
  pushad
  cmp  dword ptr CurrentHandleExceptionSynLog,0
  jz   @oldproc
  mov  eax,TargetFrame
  mov  edx,ExceptionRecord
  call LogExcept
@oldproc:
  popad
  pop ebp // hidden push ebp at asm level
{$ifdef DELPHI5OROLDER}
  jmp RtlUnwind
{$else}
  jmp oldUnWindProc
{$endif}
end;

{$endif WITH_VECTOREXCEPT}

{$endif NOEXCEPTIONINTERCEPT}

procedure TSynLogFamily.SetDestinationPath(const value: TFileName);
begin
  fDestinationPath := IncludeTrailingPathDelimiter(value);
end;

procedure TSynLogFamily.SetLevel(aLevel: TSynLogInfos);
begin
  // ensure BOTH Enter+Leave are always selected at once, if any is set
  if sllEnter in aLevel then
    include(aLevel,sllLeave) else
  if sllLeave in aLevel then
    include(aLevel,sllEnter);
  fLevel := aLevel;
{$ifndef NOEXCEPTIONINTERCEPT}
  // intercept exceptions, if necessary
  fHandleExceptions := (sllExceptionOS in aLevel) or (sllException in aLevel);
  if fHandleExceptions and (CurrentHandleExceptionSynLog=nil) then begin
    SynLog; // force define CurrentHandleExceptionSynLog
  {$ifdef WITH_VECTOREXCEPT}
    AddVectoredExceptionHandler :=
      GetProcAddress(GetModuleHandle(kernel32),'AddVectoredExceptionHandler');
    // RemoveVectoredContinueHandler() is available under 64 bit editions only
    if Assigned(AddVectoredExceptionHandler) then
      // available since Windows XP
      AddVectoredExceptionHandler(0,@SynLogVectoredHandler);
  {$else WITH_VECTOREXCEPT}
  {$ifdef DELPHI5OROLDER}
    PatchCallRtlUnWind;
  {$else}
    oldUnWindProc := RTLUnwindProc;
  {$endif}
    RTLUnwindProc := @SynRtlUnwind;
  {$endif WITH_VECTOREXCEPT}
  end;
{$endif NOEXCEPTIONINTERCEPT}
end;

procedure TSynLogFamily.SetEchoToConsole(aEnabled: TSynLogInfos);
begin
  if (self=nil) or (aEnabled=fEchoToConsole) then
    exit;
  fEchoToConsole := aEnabled;
end;

constructor TSynLogFamily.Create(aSynLog: TSynLogClass);
begin
  fSynLogClass := aSynLog;
  if SynLogFamily=nil then
    GarbageCollectorFreeAndNil(SynLogFamily,TList.Create);
  fIdent := SynLogFamily.Add(self);
  {$ifdef MSWINDOWS}
  fDestinationPath := ExtractFilePath(paramstr(0)); // use .exe path
  {$endif}
  fDefaultExtension := '.log';
  fArchivePath := fDestinationPath;
  fArchiveAfterDays := 7;
  fRotateFileAtHour := -1;
  fBufferSize := 4096;
  fStackTraceLevel := 20;
  {$ifndef FPC}
  if DebugHook<>0 then // never let stManualAndAPI trigger AV within the IDE
    fStackTraceUse := stOnlyAPI;
  {$endif}
  fExceptionIgnore := TList.Create;
  fLevelStackTrace :=
    [sllError,sllException,sllExceptionOS,sllFail,sllLastError,sllStackTrace];
end;

function TSynLogFamily.CreateSynLog: TSynLog;
var i: integer;
begin
  if SynLogFileList=nil then
    GarbageCollectorFreeAndNil(SynLogFileList,TObjectListLocked.Create);
  SynLogFileList.Lock;
  try
    result := fSynLogClass.Create(self);
    i := SynLogFileList.Add(result);
    if fPerThreadLog=ptOneFilePerThread then 
      if (fRotateFileCount=0) and (fRotateFileSize=0) and (fRotateFileAtHour<0) then 
        SynLogFileIndexThreadVar[fIdent] := i+1 else begin
        fPerThreadLog := ptIdentifiedInOnFile; // excluded by rotation
        fGlobalLog := result;
      end else
      fGlobalLog := result;
  finally
    SynLogFileList.UnLock;
  end;
end;

{$ifdef MSWINDOWS}

var
  AutoFlushThread: THandle = 0;
  AutoFlushSecondElapsed: cardinal;

procedure AutoFlushProc(P: pointer); stdcall;  // TThread not needed here
var i: integer;
begin
  repeat
    for i := 1 to 10 do begin // check every second for pending data
      Sleep(100);
      if AutoFlushThread=0 then
        exit; // avoid GPF
    end;
    if SynLogFileList=nil then
      continue; // nothing to flush
    inc(AutoFlushSecondElapsed);
    SynLogFileList.Lock;
    try
      for i := 0 to SynLogFileList.Count-1 do
      with TSynLog(SynLogFileList.List[i]) do
        if AutoFlushThread=0 then
          break else // avoid GPF
        if (fFamily.fAutoFlush<>0) and (fWriter<>nil) and
           (AutoFlushSecondElapsed mod fFamily.fAutoFlush=0) then
          if fWriter.B-fWriter.fTempBuf>1 then begin
            if not IsMultiThread then
              if not fWriterStream.InheritsFrom(TFileStream) then
                IsMultiThread := true; // only TFileStream is thread-safe
            Flush(false); // write pending data
          end;
     finally
       SynLogFileList.UnLock;
     end;
  until false;
  ExitThread(0);
end;

procedure TSynLogFamily.SetAutoFlush(TimeOut: cardinal);
var ID: cardinal;
begin
  fAutoFlush := TimeOut;
  if (AutoFlushThread=0) and (TimeOut<>0) {$ifndef FPC}and (DebugHook=0){$endif} then begin
    AutoFlushThread := CreateThread(nil,0,@AutoFlushProc,nil,0,ID);
    AutoFlushSecondElapsed := 0;
  end;
end;

{$endif}

destructor TSynLogFamily.Destroy;
var SR: TSearchRec;
    OldTime: integer;
    aTime: TDateTime;
    Y,M,D: word;
    aOldLogFileName, aPath: TFileName;
    tmp: array[0..11] of AnsiChar;
begin
  fDestroying := true;
  EchoRemoteStop;
  {$ifdef MSWINDOWS}
  if AutoFlushThread<>0 then
    AutoFlushThread := 0; // mark thread released to avoid GPF in AutoFlushProc
  {$endif}
  ExceptionIgnore.Free;
  try
    if Assigned(OnArchive) then
    if FindFirst(DestinationPath+'*'+DefaultExtension,faAnyFile,SR)=0 then
    try
      if ArchiveAfterDays<0 then
        ArchiveAfterDays := 0;
      OldTime := DateTimeToFileDate(Now-ArchiveAfterDays);
      repeat
        {$ifndef DELPHI5OROLDER}
        {$WARN SYMBOL_DEPRECATED OFF} // for SR.Time
        {$endif}
        if (SR.Name[1]='.') or (faDirectory and SR.Attr<>0) or
           (SR.Time>OldTime) then
          continue;
        {$ifndef DELPHI5OROLDER}
        {$WARN SYMBOL_DEPRECATED ON}
        {$endif}
        aOldLogFileName := DestinationPath+SR.Name;
        if aPath='' then begin
          aTime := FileAgeToDateTime(aOldLogFileName);
          if (aTime=0) or
             not DirectoryExists(ArchivePath+'log') and
             not CreateDir(ArchivePath+'log') then
            break;
          DecodeDate(aTime,Y,M,D);
          PCardinal(@tmp)^ := ord('l')+ord('o') shl 8+ord('g') shl 16+ord('\') shl 24;
          YearToPChar(Y,@tmp[4]);
          PWord(@tmp[8])^ := TwoDigitLookupW[M];
          PWord(@tmp[10])^ := ord('\');
          aPath := ArchivePath+Ansi7ToString(tmp,11);
        end;
        OnArchive(aOldLogFileName,aPath);
      until FindNext(SR)<>0;
    finally
      try
        OnArchive('',aPath); // mark no more .log file to archive -> close .zip
      finally
        FindClose(SR);
      end;
    end;
  finally
    {$ifdef MSWINDOWS}
    if AutoFlushThread<>0 then
      CloseHandle(AutoFlushThread); // release background thread once for all
    {$endif}
    inherited;
  end;
end;

function TSynLogFamily.SynLog: TSynLog;
var ndx: integer;
begin
  if self=nil then
    result := nil else begin
    if (fPerThreadLog=ptOneFilePerThread) and (fRotateFileCount=0) and
       (fRotateFileSize=0) and (fRotateFileAtHour<0) then begin
      ndx := SynLogFileIndexThreadVar[fIdent]-1;
      if ndx>=0 then // SynLogFileList.Lock/Unlock is not mandatory here
        result := SynLogFileList.List[ndx] else
        result := CreateSynLog;
    end else // for ptMergedInOneFile and ptIdentifiedInOnFile
      if fGlobalLog<>nil then
        result := fGlobalLog else
        result := CreateSynLog;
{$ifndef NOEXCEPTIONINTERCEPT}
    if fHandleExceptions and (CurrentHandleExceptionSynLog<>result) then
      CurrentHandleExceptionSynLog := result;
{$endif}
  end;
end;

procedure TSynLogFamily.EchoRemoteStart(aClient: TObject;
  const aClientEvent: TOnTextWriterEcho; aClientOwnedByFamily: boolean);
var i: integer;
begin
  EchoRemoteStop;
  fEchoRemoteClient := aClient;
  fEchoRemoteEvent := aClientEvent;
  fEchoRemoteClientOwned := aClientOwnedByFamily;
  SynLogFileList.Lock;
  try
    for i := 0 to SynLogFileList.Count-1 do
      if TSynLog(SynLogFileList.List[i]).fFamily=self then
        TSynLog(SynLogFileList.List[i]).fWriter.EchoAdd(fEchoRemoteEvent);
  finally
    SynLogFileList.Unlock;
  end;
end;

procedure TSynLogFamily.EchoRemoteStop;
var i: integer;
begin
  if fEchoRemoteClient=nil then
    exit;
  if fEchoRemoteClientOwned then
    try
      try
        fEchoRemoteEvent(nil,sllClient,
        {$ifdef DELPHI5OROLDER}
          NowToString(false)+'00'+LOG_LEVEL_TEXT[sllClient]+
          '    Remote Client Disconnected');
        {$else}
          FormatUTF8('%00%    Remote Client % Disconnected',
          [NowToString(false),LOG_LEVEL_TEXT[sllClient],self]));
        {$endif}
      finally
        FreeAndNil(fEchoRemoteClient);
      end;
    except
      on Exception do ;
    end else
    fEchoRemoteClient := nil;
  if SynLogFileList<>nil then begin
    SynLogFileList.Lock;
    try
      for i := 0 to SynLogFileList.Count-1 do
        if TSynLog(SynLogFileList.List[i]).fFamily=self then
          TSynLog(SynLogFileList.List[i]).fWriter.EchoRemove(fEchoRemoteEvent);
    finally
      SynLogFileList.Unlock;
    end;
  end;
  fEchoRemoteEvent := nil;
end;


{ TSynLog }

const
  // maximum of thread IDs which can exist for a process
  // - shall be a power of 2 (used for internal TSynLog.fThreadHash)
  // - with the default 1MB stack size, max is around 2000 threads for Win32
  // - thread IDs are recycled when released, and you always should use a thread
  // pool (like we do for all our mORMot servers, including http.sys based)
  MAXLOGTHREAD = 1 shl 15;

procedure TSynLog.LockAndGetThreadContextInternal(ID: DWORD);
var hash: integer;
    Pass2: boolean;
label storendx;
begin
  hash := ID and (MAXLOGTHREAD-1);
  Pass2 := false;
  fThreadIndex := fThreadHash[hash];
  if fThreadIndex<>0 then
    repeat
      if fThreadContexts[fThreadIndex-1].ID=ID then
        goto storendx; // found the ID
      // hash collision -> try next item in fThreadHash[] if possible
      if hash>=MAXLOGTHREAD then
        if Pass2 then begin
         fThreadIndex := 0;
         goto storendx;
        end else begin
          hash := 0;
          Pass2 := true;
        end else
        inc(hash);
      fThreadIndex := fThreadHash[hash];
    until fThreadIndex=0;
  // here we know that fThreadIndex=fThreadHash[hash]=0
  if fThreadContextCount<MAXLOGTHREAD then begin
    if fThreadContextCount>=length(fThreadContexts) then
      SetLength(fThreadContexts,fThreadContextCount+256);
    fThreadContexts[fThreadContextCount].ID := ID;
    inc(fThreadContextCount);
    fThreadHash[hash] := fThreadContextCount;
  end;
  fThreadIndex := fThreadContextCount;
storendx:
  fThreadID := ID;
  fThreadContext := @fThreadContexts[fThreadIndex];
end;

procedure TSynLog.LockAndGetThreadContext;
var ID: DWORD;
begin
  EnterCriticalSection(fThreadLock);
  ID := GetCurrentThreadId;
  if ID<>fThreadID then
    LockAndGetThreadContextInternal(ID);
end;

function TSynLog._AddRef: Integer;
begin
  if fFamily.Level*[sllEnter,sllLeave]<>[] then begin
    LockAndGetThreadContext;
    with fThreadContext^ do
    if RecursionCount>0 then
      with Recursion[RecursionCount-1] do begin
        if (RefCount=0) and (sllEnter in fFamily.Level) then
          DoEnterLeave(sllEnter);
        inc(RefCount);
        result := RefCount;
      end else
      result := 1; // should never be 0 (mark release of TSynLog instance)
    LeaveCriticalSection(fThreadLock);
  end else
    result := 1;
end;

{$ifdef MSWINDOWS}
var
  RtlCaptureStackBackTraceRetrieved: (btUntested, btOK, btFailed) = btUntested;
  RtlCaptureStackBackTrace: function(FramesToSkip, FramesToCapture: cardinal;
    BackTrace, BackTraceHash: pointer): byte; stdcall;
{$endif}

function TSynLog._Release: Integer;
{$ifdef MSWINDOWS}
{$ifndef CPU64}
var aStackFrame: PtrInt;
{$endif}
{$endif}
begin
  if fFamily.Level*[sllEnter,sllLeave]<>[] then begin
    LockAndGetThreadContext;
    with fThreadContext^ do
    if RecursionCount>0 then begin
      with Recursion[RecursionCount-1] do begin
        dec(RefCount);
        if RefCount=0 then begin
          if sllLeave in fFamily.Level then begin
            if MethodName=nil then begin
              {$ifdef MSWINDOWS}
              {$ifdef CPU64}
              if RtlCaptureStackBackTrace(1,1,@Caller,nil)=0 then
                Caller := 0 else
                dec(Caller,5); // ignore caller op codes
              {$else}
              asm
                mov eax,[ebp+16] // +4->_IntfClear +16->initial caller
                mov aStackFrame,eax
              end;
              Caller := aStackFrame-5;
              {$endif}
              {$else}
              Caller := 0; // no stack trace yet under Linux
              {$endif}
            end;
            DoEnterLeave(sllLeave);
          end;
          dec(RecursionCount);
        end;
        result := RefCount;
      end;
    end else
      result := 1; // should never be 0 (mark release of TSynLog)
    LeaveCriticalSection(fThreadLock);
  end else
    result := 1;
end;

constructor TSynLog.Create(aFamily: TSynLogFamily);
begin
  if aFamily=nil then
    aFamily := Family;
  fFamily := aFamily;
  InitializeCriticalSection(fThreadLock);
  {$ifdef MSWINDOWS}
  if RtlCaptureStackBackTraceRetrieved=btUntested then begin
    if OSVersion<wXP then
      RtlCaptureStackBackTraceRetrieved := btFailed else begin
     @RtlCaptureStackBackTrace := GetProcAddress(GetModuleHandle(kernel32),'RtlCaptureStackBackTrace');
     if @RtlCaptureStackBackTrace=nil then
       RtlCaptureStackBackTraceRetrieved := btFailed else
       RtlCaptureStackBackTraceRetrieved := btOK;
    end;
  end;
  {$ifdef CPU64}
  assert(RtlCaptureStackBackTraceRetrieved=btOK);
  {$endif}
  {$endif}
  SetLength(fThreadHash,MAXLOGTHREAD);
  SetLength(fThreadContexts,256);
end;

destructor TSynLog.Destroy;
begin
{$ifndef NOEXCEPTIONINTERCEPT}
  if fFamily.fHandleExceptions and (CurrentHandleExceptionSynLog=self) then
    CurrentHandleExceptionSynLog := nil;
{$endif}
  Flush(true);
  fWriterStream.Free;
  fWriter.Free;
  DeleteCriticalSection(fThreadLock);
  inherited;
end;

procedure TSynLog.CloseLogFile;
begin
  if fWriter=nil then
    exit;
  EnterCriticalSection(fThreadLock);
  try
    fWriter.Flush;
    FreeAndNil(fWriterStream);
    FreeAndNil(fWriter);
  finally
    LeaveCriticalSection(fThreadLock);
  end;
end;

procedure TSynLog.Release;
begin
  SynLogFileList.Lock;
  try
    CloseLogFile;
    SynLogFileList.Remove(self);
    if fFamily.fPerThreadLog=ptOneFilePerThread then
      SynLogFileIndexThreadVar[fFamily.fIdent] := 0;
  finally
    SynLogFileList.Unlock;
  end;
  Free;
end;

procedure TSynLog.Flush(ForceDiskWrite: boolean);
begin
  if fWriter=nil then
    exit;
  EnterCriticalSection(fThreadLock);
  try
    fWriter.Flush;
    {$ifdef MSWINDOWS}
    if ForceDiskWrite and fWriterStream.InheritsFrom(TFileStream) then
      FlushFileBuffers(TFileStream(fWriterStream).Handle);
    {$endif}
  finally
    LeaveCriticalSection(fThreadLock);
  end;
end;

{$ifdef FPC}
function TSynLog.QueryInterface({$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} iid : tguid;out obj) : longint;{$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
begin
  Result := E_NOINTERFACE;
end;
{$else}
function TSynLog.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  Result := E_NOINTERFACE;
end;
{$endif}

class function TSynLog.Add: TSynLog;
begin
  result := Family.SynLog;
end;

class function TSynLog.Enter(aInstance: TObject; aMethodName: PUTF8Char;
  aMethodNameLocal: boolean): ISynLog;
var aSynLog: TSynLog;
    aFamily: TSynLogFamily;
    aStackFrame: PtrUInt;
begin
  // inlined aSynLog := Family.SynLog
  if PtrInt(self)=0 then begin
    result := nil;
    exit;
  end;
  aFamily := PPointer(PtrInt(Self)+vmtAutoTable)^;
  if aFamily=nil then
    aSynLog := FamilyCreate.SynLog else
    aSynLog := aFamily.SynLog;
  // recursively store parameters
  with aSynLog do
  if sllEnter in fFamily.fLevel then begin
    LockAndGetThreadContext;
    with fThreadContext^ do begin
      if RecursionCount=RecursionCapacity then begin
        inc(RecursionCapacity,32);
        SetLength(Recursion,RecursionCapacity);
      end;
      {$ifdef MSWINDOWS}
      {$ifdef CPU64}
      if RtlCaptureStackBackTrace(1,1,@aStackFrame,nil)=0 then
        aStackFrame := 0 else
        dec(aStackFrame,5); // ignore call TSynLog.Enter op codes
      {$else}
      asm
        mov eax,[ebp+4]  // retrieve caller EIP from push ebp; mov ebp,esp
        sub eax,5        // ignore call TSynLog.Enter op codes
        mov aStackFrame,eax
      end;
      {$endif}
      {$else}
      aStackFrame := 0; // No stack trace yet under Linux
      {$endif}
      with Recursion[RecursionCount] do begin
        Instance := aInstance;
        if aInstance=nil then
          ClassType := pointer(aInstance) else
          ClassType := PPointer(aInstance)^;
        MethodName := aMethodName;
        if aMethodNameLocal then
          MethodNameLocal := mnEnter else
          MethodNameLocal := mnAlways;
        Caller := aStackFrame;
        RefCount := 0;
      end;
      inc(RecursionCount);
      LeaveCriticalSection(fThreadLock);
    end;
  end;
  // copy to ISynLog interface -> will call TSynLog._AddRef
  result := aSynLog;
end;

class function TSynLog.FamilyCreate: TSynLogFamily;
var PVMT: pointer;
begin // private sub function makes the code faster in most case
  if not InheritsFrom(TSynLog) then
    // invalid call
    result := nil else begin
    // create the properties information from RTTI
    result := TSynLogFamily.Create(self);
    // store the TSynLogFamily  instance into AutoTable unused VMT entry
    PVMT := pointer(PtrInt(self)+vmtAutoTable);
    if PPointer(PVMT)^<>nil then
      raise ESynException.CreateUTF8('%.AutoTable VMT entry already set',[self]);
    PatchCodePtrUInt(PVMT,PtrUInt(result),true); // LeaveUnprotected=true
    // register to the internal garbage collection (avoid memory leak)
    // ALF //
    // Does not work under ARM ... I do not know why !!
    {$ifdef CPUARM}
    {$WARNING 'This code (GarbageCollectorFreeAndNil(PVMT^,result);) does not work on ARM.'}
    {$else}
    GarbageCollectorFreeAndNil(PVMT^,result); // set to nil at finalization
    {$endif}
  end;
end;

{$ifdef PUREPASCAL}
class function TSynLog.Family: TSynLogFamily;
begin
  if Self<>nil then begin
    result := PPointer(PtrInt(Self)+vmtAutoTable)^;
    if result=nil then
      result := FamilyCreate;
  end else
    result := nil;
end;
{$else}
class function TSynLog.Family: TSynLogFamily;
asm
  or eax,eax
  jz @null
  mov edx,[eax+vmtAutoTable]
  or edx,edx
  jz FamilyCreate
  mov eax,edx
@null:
end;
{$endif}

type
  TSynLogVoid = class(TSynLog);

class function TSynLog.Void: TSynLogClass;
begin
  TSynLogVoid.Family.Level := [];
  result := TSynLogVoid;
end;

function TSynLog.Instance: TSynLog;
begin
  result := self;
end;

function TSynLog.ConsoleEcho(Sender: TTextWriter; Level: TSynLogInfo;
  const Text: RawUTF8): boolean;
{$ifdef MSWINDOWS}
var tmp: AnsiString;
{$endif}
const LOGCOLORS: array[TSynLogInfo] of TConsoleColor = (
//    sllNone, sllInfo, sllDebug, sllTrace, sllWarning, sllError, sllEnter, sllLeave
  ccLightGray,ccWhite,ccLightGray,ccLightBlue,ccBrown,ccLightRed,ccGreen,ccGreen,
//    sllLastError, sllException, sllExceptionOS, sllMemory, sllStackTrace,
  ccLightRed, ccLightRed, ccLightRed, ccLightGray, ccCyan,
//    sllFail, sllSQL, sllCache, sllResult, sllDB, sllHTTP, sllClient, sllServer,
  ccLightRed, ccBrown, ccBlue, ccLightCyan, ccMagenta, ccCyan, ccLightCyan, ccLightCyan,
//    sllServiceCall, sllServiceReturn, sllUserAuth,
  ccLightMagenta, ccLightMagenta, ccMagenta,
//    sllCustom1, sllCustom2, sllCustom3, sllCustom4, sllNewRun
  ccLightGray, ccLightGray,ccLightGray,ccLightGray,ccLightMagenta);
begin
  result := true;
  if not (Level in fFamily.fEchoToConsole) then
    exit;
  {$ifdef MSWINDOWS}
  if StdOut=0 then
    exit;
  tmp := CurrentAnsiConvert.UTF8ToAnsi(Text);
  TextColor(LOGCOLORS[Level]);
  AnsiToOem(pointer(tmp),pointer(tmp));
  tmp := tmp+#13#10;
  FileWrite(stdOut,PByte(tmp)^,length(tmp));
  TextColor(ccLightGray);
  {$else}
  TextColor(LOGCOLORS[Level]);
  writeln(#13#10,Text,#13#10);
  TextColor(ccLightGray);
  {$endif}
end;

{$ifndef DELPHI5OROLDER}
procedure TSynLog.Log(Level: TSynLogInfo; TextFmt: PWinAnsiChar; const TextArgs: array of const;
  aInstance: TObject);
begin
  if (self<>nil) and (Level in fFamily.fLevel) then
    LogInternal(Level,TextFmt,TextArgs,aInstance);
end;

procedure TSynLog.Log(Level: TSynLogInfo; TextFmt: PWinAnsiChar; const TextArg: RawUTF8;
  aInstance: TObject=nil);
begin
  if (self<>nil) and (Level in fFamily.fLevel) then
    LogInternal(Level,TextFmt,[TextArg],aInstance);
end;

procedure TSynLog.Log(Level: TSynLogInfo; TextFmt: PWinAnsiChar; const TextArg: Int64;
  aInstance: TObject=nil);
begin
  if (self<>nil) and (Level in fFamily.fLevel) then
    LogInternal(Level,TextFmt,[TextArg],aInstance);
end;
{$endif}

procedure TSynLog.Log(Level: TSynLogInfo; const Text: RawUTF8; aInstance: TObject;
  TextTruncateAtLength: integer);
begin
  if (self<>nil) and (Level in fFamily.fLevel) then
    LogInternal(Level,Text,aInstance,TextTruncateAtLength);
end;

procedure TSynLog.LogLines(Level: TSynLogInfo; LinesToLog: PUTF8Char; aInstance: TObject;
  const IgnoreWhenStartWith: PAnsiChar);
procedure DoLog(LinesToLog: PUTF8Char);
var s: RawUTF8;
begin
  while LinesToLog<>nil do begin
    s := trim(GetNextLine(LinesToLog,LinesToLog));
    if s<>'' then
      if (IgnoreWhenStartWith=nil) or not IdemPChar(pointer(s),IgnoreWhenStartWith) then
        LogInternal(Level,s,aInstance,maxInt);
  end;
end;
begin
  if (self<>nil) and (Level in fFamily.fLevel) then
    DoLog(LinesToLog);
end;

procedure TSynLog.Log(Level: TSynLogInfo; aInstance: TObject);
begin
  if (self<>nil) and (Level in fFamily.fLevel) then
    if aInstance<>nil then
      LogInternal(Level,'',aInstance,maxInt) else
      LogInternal(Level,'Instance=nil',nil,maxInt);
end;

procedure TSynLog.Log(Level: TSynLogInfo; aName: PWinAnsiChar;
  aTypeInfo: pointer; var aValue; Instance: TObject=nil);
begin
  if (self<>nil) and (Level in fFamily.fLevel) then
    LogInternal(Level,aName,aTypeInfo,aValue,Instance);
end;

procedure TSynLog.Log(Level: TSynLogInfo);
var aCaller: PtrUInt;
    LastError: DWORD;
begin
  if Level=sllLastError then
    LastError := GetLastError else
    LastError := 0;
  if (self<>nil) and (Level in fFamily.fLevel) then
  if LogHeaderLock(Level) then
  try
    if LastError<>0 then
      AddErrorMessage(LastError);
    {$ifdef MSWINDOWS}
    {$ifdef CPU64}
    if RtlCaptureStackBackTrace(1,1,@aCaller,nil)=0 then
      aCaller := 0 else
      dec(aCaller,5); // ignore call TSynLog.Enter op codes
    {$else}
    asm
      mov eax,[ebp+4]  // retrieve caller EIP from push ebp; mov ebp,esp
      sub eax,5        // ignore call TSynLog.Enter op codes
      mov aCaller,eax
    end;
    {$endif}
    {$else}
    aCaller := 0; // no stack trace yet under Linux
    {$endif}
    TSynMapFile.Log(fWriter,aCaller);
  finally
    LogTrailerUnLock(Level);
  end;
end;

{$ifndef DELPHI5OROLDER}
class procedure TSynLog.DebuggerNotify(const Args: array of const; Format: PWinAnsiChar=nil);
var Msg: RawUTF8;
begin
  if Format<>nil then begin
    Msg := FormatUTF8(PUTF8Char(Format),Args);
    Add.LogInternal(sllWarning,Msg,nil,maxInt);
    {$ifdef MSWINDOWS}
    OutputDebugStringA(pointer(Msg));
    {$endif}
  end;
  {$ifndef FPC_OR_PUREPASCAL}
  if DebugHook<>0 then
    asm int 3 end; // force manual breakpoint if tests are run from the IDE
  {$endif}
end;
{$endif}

procedure TSynLog.LogFileHeader;
{$ifdef MSWINDOWS}
var Env: PAnsiChar;
    P: PUTF8Char;
    L: Integer;
{$endif}
var WithinEvents: boolean;
procedure NewLine;
begin
  if WithinEvents then begin
    fWriter.AddEndOfLine(sllNewRun);
    LogCurrentTime;
    fWriter.AddShort(LOG_LEVEL_TEXT[sllNewRun]);
  end else
    fWriter.Add(#13);
end;
begin
  {$ifdef MSWINDOWS}
  if not QueryPerformanceFrequency(fFrequencyTimeStamp) then begin
    fFamily.HighResolutionTimeStamp := false;
    fFrequencyTimeStamp := 0;
  end else
  {$endif}
    if (fFileRotationSize>0) or (fFileRotationNextHour<>0) then
      fFamily.HighResolutionTimeStamp := false;
  {$ifdef MSWINDOWS}
  ExeVersionRetrieve;
  {$endif}
  if InstanceMapFile=nil then
    GarbageCollectorFreeAndNil(InstanceMapFile,TSynMapFile.Create);
  WithinEvents := fWriter.fTotalFileSize>0;
  // array of const is buggy under Delphi 5 :( -> use fWriter.Add*()
  {$ifdef MSWINDOWS}
  with ExeVersion, SystemInfo, OSVersionInfo, fWriter do begin
    if WithinEvents then begin
      LogCurrentTime;
      AddShort(LOG_LEVEL_TEXT[sllNewRun]);
      AddChars('=',50);
      NewLine;
    end;
    AddString(ProgramFullSpec);
    NewLine;
    AddShort('Host=');  AddString(Host);
    AddShort(' User='); AddString(User);
    AddShort(' CPU=');  Add(dwNumberOfProcessors); Add('*');
    Add(wProcessorArchitecture); Add('-'); Add(wProcessorLevel); Add('-');
    Add(wProcessorRevision);
    AddShort(' OS='); Add(ord(OSVersion)); Add('.'); Add(wServicePackMajor);
    Add('='); Add(dwMajorVersion); Add('.'); Add(dwMinorVersion); Add('.');
    Add(dwBuildNumber);
    AddShort(' Wow64='); Add(integer(IsWow64));
    AddShort(' Freq='); Add(fFrequencyTimeStamp);
    if IsLibrary then begin
      AddShort(' Instance=');
      AddNoJSONEscapeString(InstanceFileName);
    end;
    NewLine;
    AddShort('Environment variables=');
    Env := GetEnvironmentStringsA;
    P := pointer(Env);
    while P^<>#0 do begin
      L := StrLen(P);
      if (L>0) and (P^<>'=') then begin
        AddNoJSONEscape(P,L);
        Add(#9);
      end;
      inc(P,L+1);
    end;
    FreeEnvironmentStringsA(Env);
    CancelLastChar; // trim last #9
    {$else}
  with fWriter do begin
    AddShort('Host=linux User=user CPU=1*0-15-1027 OS=2.3=5.1.2600 Wow64=0 Freq=1234');
    {$endif}
    NewLine;
    AddClassName(self.ClassType);
    AddShort(' '+SYNOPSE_FRAMEWORK_VERSION+' ');
    AddDateTime(Now);
    if WithinEvents then
      AddEndOfLine(sllNone) else
      Add(#13,#13);
    Flush;
    fEchoBuf := ''; // header is not to be sent to console
  end;
  QueryPerformanceCounter(fStartTimeStamp);
  Include(fInternalFlags,logHeaderWritten);
end;

{$ifndef DELPHI5OROLDER}
{$WARN SYMBOL_DEPRECATED OFF} // for GetHeapStatus
procedure TSynLog.AddMemoryStats;
begin
  {$ifdef MSWINDOWS}
  with GetHeapStatus do
    if TotalAddrSpace<>0 then
    fWriter.Add(' AddrSpace=% Uncommitted=% Committed=% Allocated=% Free=% '+
       'FreeSmall=% FreeBig=% Unused=% Overheap=% ',
      [TotalAddrSpace,TotalUncommitted,TotalCommitted,TotalAllocated,TotalFree,
       FreeSmall,FreeBig,Unused,Overhead]);
  {$endif}
end;
{$WARN SYMBOL_DEPRECATED ON}
{$endif}

procedure TSynLog.AddErrorMessage(Error: Cardinal);
{$ifdef MSWINDOWS}
var Len: Integer;
    Buffer: array[byte] of WideChar;
begin
  Len := FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_ARGUMENT_ARRAY,
    nil, Error, 0, Buffer, SizeOf(Buffer), nil);
  while (Len>0) and (ord(Buffer[Len-1]) in [0..32,ord('.')]) do dec(Len);
  Buffer[Len] := #0;
  fWriter.Add(' ','"');
  fWriter.AddOnSameLineW(@Buffer,Len);
  fWriter.AddShort('" (');
{$else}
begin
  fWriter.AddShort('Error ');
{$endif}
  fWriter.Add(Error);
  fWriter.Add(')',' ');
end;

procedure TSynLog.LogCurrentTime;
begin
  if fFamily.HighResolutionTimeStamp
     {$ifdef MSWINDOWS}and (fFrequencyTimeStamp<>0){$endif} then begin
    QueryPerformanceCounter(fCurrentTimeStamp);
    dec(fCurrentTimeStamp,fStartTimeStamp);
    fWriter.AddBinToHexDisplay(@fCurrentTimeStamp,sizeof(fCurrentTimeStamp));
  end else
    fWriter.AddCurrentLogTime;
end;

function TSynLog.LogHeaderLock(Level: TSynLogInfo): boolean;
var i: integer;
begin
  LockAndGetThreadContext;
  try
    if fWriter=nil then
      CreateLogWriter; // file creation should be thread-safe
    if not (logHeaderWritten in fInternalFlags) then
      LogFileHeader;
    if (not (sllEnter in fFamily.Level)) and (Level in fFamily.fLevelStackTrace) then
       for i := 0 to fThreadContext^.RecursionCount-1 do begin
         fWriter.AddChars(' ',i+24-byte(fFamily.HighResolutionTimeStamp));
         AddRecursion(i,sllNone);
       end;
    LogCurrentTime;
    if fFamily.fPerThreadLog=ptIdentifiedInOnFile then
      fWriter.AddInt18ToChars3(fThreadIndex);
    fCurrentLevel := Level;
    fWriter.AddShort(LOG_LEVEL_TEXT[Level]);
    fWriter.AddChars(#9,fThreadContext^.RecursionCount-byte(Level in [sllEnter,sllLeave]));
  {$ifndef DELPHI5OROLDER}
    case Level of // handle additional text for some special error levels
      sllMemory: AddMemoryStats;
    end;
  {$endif}
    result := true;
  except
    on Exception do
     result := false ;
  end;
end;

procedure TSynLog.PerformRotation;
var currentMaxSynLZ: cardinal;
    i: integer;
    FN: array of TFileName;
begin
  fWriter.Flush;
  FreeAndNil(fWriter);
  FreeAndNil(fWriterStream);
  currentMaxSynLZ := 0;
  if not (assigned(fFamily.fOnRotate) and
          fFamily.fOnRotate(self,fFileName)) then begin
    if fFamily.fRotateFileCount>1 then begin
      SetLength(FN,fFamily.fRotateFileCount-1);
      for i := fFamily.fRotateFileCount-1 downto 1 do begin
        FN[i-1] := ChangeFileExt(fFileName,'.'+IntToStr(i)+'.synlz');
        if (currentMaxSynLZ=0) and FileExists(FN[i-1]) then
          currentMaxSynLZ := i;
      end;
      if currentMaxSynLZ=fFamily.fRotateFileCount-1 then
        DeleteFile(FN[currentMaxSynLZ-1]); // delete e.g. '9.synlz'
      for i := fFamily.fRotateFileCount-2 downto 1 do
        RenameFile(FN[i-1],FN[i]); // e.g. '8.synlz' -> '9.synlz'
      FileSynLZ(fFileName,FN[0],LOG_MAGIC); // main -> '1.synlz'
    end;
    DeleteFile(fFileName);
  end;
  CreateLogWriter;
  LogFileHeader;
end;

procedure TSynLog.LogTrailerUnLock(Level: TSynLogInfo);
begin
  try
    if Level in fFamily.fLevelStackTrace then
      AddStackTrace(nil);
    fWriter.AddEndOfLine(fCurrentLevel);
    if (fFileRotationNextHour<>0) and (GetTickCount64>=fFileRotationNextHour) then begin
      inc(fFileRotationNextHour,MSecsPerDay);
      PerformRotation;
    end else
    if (fFileRotationSize>0) and (fWriter.fTotalFileSize>fFileRotationSize) then
      PerformRotation;
  finally
    LeaveCriticalSection(fThreadLock);
  end;
end;

{$ifndef DELPHI5OROLDER}
procedure TSynLog.LogInternal(Level: TSynLogInfo; TextFmt: PWinAnsiChar;
  const TextArgs: array of const; Instance: TObject);
var LastError: cardinal;
begin
  if Level=sllLastError then
    LastError := GetLastError else
    LastError := 0;
  if LogHeaderLock(Level) then
  try
    if Instance<>nil then
      fWriter.AddInstancePointer(Instance,' ');
    fWriter.Add(TextFmt,TextArgs,twOnSameLine);
    if LastError<>0 then
      AddErrorMessage(LastError);
  finally
    LogTrailerUnLock(Level);
  end;
end;
{$endif}

procedure TSynLog.LogInternal(Level: TSynLogInfo; const Text: RawUTF8;
  Instance: TObject; TextTruncateAtLength: integer);
var LastError: cardinal;
begin
  if Level=sllLastError then
    LastError := GetLastError else
    LastError := 0;
  if LogHeaderLock(Level) then
  try
    if Text='' then begin
      if Instance<>nil then
        if Instance.InheritsFrom(Exception) then begin
          fWriter.AddInstanceName(Instance,':');
          fWriter.Add('"');
          fWriter.AddJSONEscapeString(Exception(Instance).Message);
          fWriter.Add('"');
        end else
          fWriter.WriteObject(Instance,[woFullExpand]);
    end else begin
      if Instance<>nil then
        fWriter.AddInstancePointer(Instance,' ');
      if length(Text)>TextTruncateAtLength then begin
        fWriter.AddOnSameLine(pointer(Text),TextTruncateAtLength);
        fWriter.AddShort('... (truncated) length=');
        fWriter.AddU(length(Text));
      end else
        fWriter.AddOnSameLine(pointer(Text));
    end;
    if LastError<>0 then
      AddErrorMessage(LastError);
  finally
    LogTrailerUnLock(Level);
  end;
end;

procedure TSynLog.LogInternal(Level: TSynLogInfo; aName: PWinAnsiChar;
   aTypeInfo: pointer; var aValue; Instance: TObject=nil);
begin
  if LogHeaderLock(Level) then
  try
    if Instance<>nil then
      fWriter.AddInstancePointer(Instance,' ');
    fWriter.AddNoJSONEscape(aName);
    fWriter.Add('=');
    fWriter.AddTypedJSON(aTypeInfo,aValue);
  finally
    LogTrailerUnLock(Level);
  end;
end;

procedure TSynLog.ComputeFileName;
var timeNow,hourRotate,timeBeforeRotate: TDateTime;
begin
  fFileName := fFamily.fCustomFileName;
  if fFileName='' then begin
    {$ifdef MSWINDOWS}
    ExeVersionRetrieve;
    fFileName := UTF8ToString(ExeVersion.ProgramName);
    if fFamily.IncludeComputerNameInFileName then
      fFileName := fFileName+' ('+UTF8ToString(ExeVersion.Host)+')';
    {$else}
    split(ExtractFileName(ParamStr(0)),'.',fFileName);
    {$endif}
  end;
  fFileRotationSize := 0;
  if fFamily.fRotateFileCount>0 then begin
    if fFamily.fRotateFileSize>0 then
      fFileRotationSize := fFamily.fRotateFileSize shl 10; // size KB -> B
    if fFamily.fRotateFileAtHour in [0..23] then begin
      hourRotate := EncodeTime(fFamily.fRotateFileAtHour,0,0,0);
      timeNow := Time;
      if hourRotate<timeNow then
        hourRotate := hourRotate+1; // trigger will be tomorrow
      timeBeforeRotate := hourRotate-timeNow;
      fFileRotationNextHour := GetTickCount64+trunc(timeBeforeRotate*MSecsPerDay);
    end;
  end;
  if (fFileRotationSize=0) and (fFileRotationNextHour=0) then
    fFileName := fFileName+' '+Ansi7ToString(NowToString(false));
  {$ifdef MSWINDOWS}
  if IsLibrary and (fFamily.fCustomFileName='') then
    fFileName := fFileName+' '+ExtractFileName(GetModuleName(HInstance));
  {$endif}
  if fFamily.fPerThreadLog=ptOneFilePerThread then
    fFileName := fFileName+' '+IntToString(GetCurrentThreadId);
  fFileName := fFamily.fDestinationPath+fFileName+fFamily.fDefaultExtension;
end;

procedure TSynLog.CreateLogWriter;
var i,retry: integer;
    exists: boolean;
begin
  if fWriterStream=nil then begin
    ComputeFileName;
    if fFamily.NoFile then
      fWriterStream := TFakeWriterStream.Create else begin
      if FileExists(fFileName) then
        case fFamily.FileExistsAction of
        acOverwrite:
          DeleteFile(fFileName);
        acAppend:
          Include(fInternalFlags,logHeaderWritten);
        end;
      for retry := 0 to 2 do begin
        for i := 1 to 10 do
        try
          exists := FileExists(fFileName);
          if exists and (fFamily.FileExistsAction<>acOverwrite) then begin
            if fFamily.FileExistsAction=acAppend then
              Include(fInternalFlags,logHeaderWritten);
          end else
          if (fFileRotationSize=0) or not exists then
            TFileStream.Create(fFileName,fmCreate).Free;   // create a void file
          fWriterStream := TFileStream.Create(fFileName,
            fmOpenReadWrite or fmShareDenyWrite); // open with read sharing
          break;
        except
          on Exception do
            Sleep(100);
        end;
        if fWriterStream<>nil then
          break;
        fFileName := ChangeFileExt(fFileName,'-'+fFamily.fDefaultExtension);
      end;
    end;
    if fWriterStream=nil then // go on if file creation fails (e.g. RO folder)
      fWriterStream := TFakeWriterStream.Create;
    if (fFileRotationSize>0) or (fFamily.FileExistsAction<>acOverwrite) then
      fWriterStream.Seek(0,soFromEnd); // in rotation mode, append at the end
  end;
  if fWriterClass=nil then
    fWriterClass := TTextWriter;
  if fWriter=nil then
    fWriter := fWriterClass.Create(fWriterStream,fFamily.BufferSize);
  fWriter.EndOfLineCRLF := fFamily.EndOfLineCRLF;
  if integer(fFamily.EchoToConsole)<>0 then
    fWriter.EchoAdd(ConsoleEcho);
  if Assigned(fFamily.EchoCustom) then
    fWriter.EchoAdd(fFamily.EchoCustom);
  if Assigned(fFamily.fEchoRemoteClient) then
    fWriter.EchoAdd(fFamily.fEchoRemoteEvent);
end;

procedure TSynLog.AddRecursion(aIndex: integer; aLevel: TSynLogInfo);
type
  {$ifdef FPC}{$PACKRECORDS 1}{$endif}
  TTypeInfo = {$ifndef FPC}packed{$endif} record
    Kind: TTypeKind;
    Name: ShortString;
  end;
  {$ifdef FPC}{$PACKRECORDS C}{$endif}
  TClassType =
    {$ifndef FPC_REQUIRES_PROPER_ALIGNMENT}
    packed
    {$endif FPC_REQUIRES_PROPER_ALIGNMENT}
    record
     ClassType: TClass;
     ParentInfo: pointer;
     PropCount: SmallInt;
     UnitName: ShortString;
  end;
  PTypeInfo = ^TTypeInfo;
  PClassType = ^TClassType;
var Info: PTypeInfo;
    MS: cardinal;
label DoEnt;
begin
  with fThreadContext^ do
  if cardinal(aIndex)<cardinal(RecursionCount) then
  with Recursion[aIndex] do begin
    if aLevel<>sllLeave then begin
      if ClassType<>nil then begin
        if fFamily.WithUnitName then begin
          Info := PPointer(PtrInt(ClassType)+vmtTypeInfo)^;
          if Info<>nil then begin
            {$ifdef FPC}
            fWriter.AddShort(PClassType(GetFPCTypeData(pointer(Info)))^.UnitName);
            {$else}
            fWriter.AddShort(PClassType(@Info^.Name[ord(Info^.Name[0])+1])^.UnitName);
            {$endif}
            fWriter.Add('.');
          end;
        end;
        fWriter.AddShort(PShortString(PPointer(PtrInt(ClassType)+vmtClassName)^)^);
        if Instance<>nil then begin
          fWriter.Add('(');
          fWriter.AddPointer(PtrUInt(Instance));
          fWriter.Add(')');
        end;
        fWriter.Add('.');
      end;
      if MethodName<>nil then begin
        if MethodNameLocal<>mnLeave then begin
          fWriter.AddNoJSONEscape(MethodName);
          if MethodNameLocal=mnEnter then
            MethodNameLocal := mnLeave;
        end;
      end else
        TSynMapFile.Log(fWriter,Caller);
    end;
    if fFamily.HighResolutionTimeStamp
       {$ifdef MSWINDOWS}and (fFrequencyTimeStamp<>0){$endif} then
DoEnt:case aLevel of
      sllEnter:
        EnterTimeStamp := fCurrentTimeStamp;
      sllLeave: begin
        {$ifdef MSWINDOWS}
        if fFrequencyTimeStamp=0 then
          MS := 0 else // avoid div per 0 exception
          MS := ((fCurrentTimeStamp-EnterTimeStamp)*(1000*1000))div fFrequencyTimeStamp;
        {$else}
        MS := fCurrentTimeStamp-EnterTimeStamp;
        {$endif}
        fWriter.AddMicroSec(MS);
      end;
      end else
    if aLevel in [sllEnter,sllLeave] then begin
      QueryPerformanceCounter(fCurrentTimeStamp);
      dec(fCurrentTimeStamp,fStartTimeStamp);
      goto DoEnt;
    end;
  end;
  fWriter.AddEndOfLine(aLevel);
end;

procedure TSynLog.DoEnterLeave(aLevel: TSynLogInfo);
begin
  if LogHeaderLock(aLevel) then
  try
    AddRecursion(fThreadContext^.RecursionCount-1,aLevel);
  finally
    LeaveCriticalSection(fThreadLock);
  end;
end;

const
  MINIMUM_EXPECTED_STACKTRACE_DEPTH = 2;

procedure TSynLog.AddStackTrace(Stack: PPtrUInt);
{$ifdef MSWINDOWS}
{$ifndef FPC}
{$ifndef CPU64}
procedure AddStackManual(Stack: PPtrUInt);
  function check2(xret: PtrUInt): Boolean;
  var i: PtrUInt;
  begin
    result := true;
    for i := 2 to 7 do
      if PWord(xret-i)^ and $38FF=$10FF then
        exit;
    result := false;
  end;
var st, max_stack, min_stack, depth: PtrUInt;
{$ifndef NOEXCEPTIONINTERCEPT}
    prevState: TSynLog;
{$endif}
begin
  depth := fFamily.StackTraceLevel;
  if Stack=nil then // if no Stack pointer set, retrieve current one
    asm
      mov eax,ebp // push ebp; mov ebp,esp done at begin level above
      mov Stack,eax
    end;
  asm
    mov eax,fs:[18h]
    mov ecx,dword ptr [eax+4]
    mov max_stack,ecx
    mov ecx,dword ptr [eax+8]
    mov min_stack,ecx
  end;
  fWriter.AddShort(' stack trace ');
  {$ifndef NOEXCEPTIONINTERCEPT}
  prevState := CurrentHandleExceptionSynLog;
  CurrentHandleExceptionSynLog := nil; // for IsBadCodePtr
  try
  {$endif}
    if PtrUInt(stack)>=min_stack then
    try
      while (PtrUInt(stack)<max_stack) do begin
        st := stack^;
        if ((st>max_stack) or (st<min_stack)) and
           not IsBadReadPtr(pointer(st-8),12) and
           ((pByte(st-5)^=$E8) or check2(st)) then begin
          TSynMapFile.Log(fWriter,st); // will ignore any TSynLog.* methods
          dec(depth);
          if depth=0 then break;
        end;
        inc(stack);
      end;
    except
      // just ignore any access violation here
    end;
  {$ifndef NOEXCEPTIONINTERCEPT}
  finally
    CurrentHandleExceptionSynLog := prevState;
  end;
  {$endif}
end;
{$endif}
{$endif}
var n, i: integer;
    BackTrace: array[byte] of PtrUInt;
{$ifndef NOEXCEPTIONINTERCEPT}
    prevState: TSynLog;
{$endif}
begin
  if fFamily.StackTraceLevel<=0 then
    exit;
  if (fFamily.StackTraceUse=stOnlyManual) or
     (RtlCaptureStackBackTraceRetrieved<>btOK) then begin
{$ifndef FPC}
{$ifndef CPU64}
    AddStackManual(Stack);
{$endif}
{$endif}
  end else begin
    {$ifndef NOEXCEPTIONINTERCEPT}
    prevState := CurrentHandleExceptionSynLog;
    CurrentHandleExceptionSynLog := nil; // for IsBadCodePtr
    try
    {$endif}
      try
        n := RtlCaptureStackBackTrace(2,fFamily.StackTraceLevel,@BackTrace,nil);
        if (n<MINIMUM_EXPECTED_STACKTRACE_DEPTH) and
           (fFamily.StackTraceUse<>stOnlyAPI) then begin
          {$ifndef FPC}
          {$ifndef CPU64}
          AddStackManual(Stack);
          {$endif}
          {$endif}
        end else begin
          fWriter.AddShort(' stack trace API ');
          for i := 0 to n-1 do
            TSynMapFile.Log(fWriter,BackTrace[i]); // will ignore any TSynLog.* methods
        end;
      except
        // just ignore any access violation here
      end;
    {$ifndef NOEXCEPTIONINTERCEPT}
    finally
      CurrentHandleExceptionSynLog := prevState;
    end;
    {$endif}
  end;
end;
{$else}
begin // do not write any stack trace yet
end;
{$endif}

{$ifdef MSWINDOWS}

procedure ExeVersionRetrieve(DefaultVersion: integer);
const EXE_FMT: PUTF8Char = '% % (%)';
var Tmp: array[byte] of AnsiChar;
    TmpSize: cardinal;
    i: integer;
begin
  with ExeVersion do
  if Version=nil then begin
    ProgramFileName := paramstr(0);
    ProgramFilePath := ExtractFilePath(ProgramFileName);
    if IsLibrary then 
      InstanceFileName := GetModuleName(HInstance) else
      InstanceFileName := ProgramFileName;
    Version := TFileVersion.Create(InstanceFileName,DefaultVersion);
    GarbageCollector.Add(Version);
    ProgramFullSpec := FormatUTF8(EXE_FMT,
      [ProgramFileName,Version.Detailed,DateTimeToIso8601(Version.BuildDateTime,True,' ')]);
    ProgramName := StringToUTF8(ExtractFileName(ProgramFileName));
    i := length(ProgramName);
    while i>0 do
      if ProgramName[i]='.' then begin
        SetLength(ProgramName,i-1);
        break;
      end else
      dec(i);
    TmpSize := sizeof(Tmp);
    GetComputerNameA(Tmp,TmpSize);
    Host := Tmp;
    TmpSize := sizeof(Tmp);
    GetUserNameA(Tmp,TmpSize);
    User := Tmp;
  end;
end;

{$endif MSWINDOWS}


{ TMemoryMapText }

constructor TMemoryMapText.Create;
begin
end;
................................................................................
function TMemoryMapText.GetString(aIndex: integer): string;
begin
  if (self=nil) or (cardinal(aIndex)>=cardinal(fCount)) then
    result := '' else
    UTF8DecodeToString(fLines[aIndex],GetLineSize(fLines[aIndex],fMapEnd),result);
end;

function GetLineContains(p,pEnd, up: PUTF8Char): boolean; {$ifdef HASINLINE}inline;{$endif}
var i: integer;
label Fnd;
begin
  if (p<>nil) and (up<>nil) then
  if pEnd=nil then
    repeat
      i := ord(p^);
................................................................................
procedure TMemoryMapText.AddInMemoryLinesClear;
begin
  dec(fCount,fAppendedLinesCount);
  fAppendedLinesCount := 0;
  fAppendedLines := nil;
end;


{ TSynLogFile }

constructor TSynLogFile.Create;
var L: TSynLogInfo;
begin
  for L := low(TSynLogInfo) to high(TSynLogInfo) do // needed by ProcessOneLine
    fLogLevelsTextMap[L] := PCardinal(@LOG_LEVEL_TEXT[L][3])^; // [3] -> e.g. 'UST4'
end;

function TSynLogFile.EventCount(const aSet: TSynLogInfos): integer;
var i: integer;
begin
  result := 0;
  for i := 0 to Count-1 do
    if fLevels[i] in aSet then
      inc(result);
end;

function TSynLogFile.LineContains(const aUpperSearch: RawUTF8; aIndex: Integer): Boolean;
begin
  if (self=nil) or (cardinal(aIndex)>=cardinal(fCount)) or (aUpperSearch='') then
    result := false else
    result := GetLineContains(PUTF8Char(fLines[aIndex])+fLineTextOffset,
      fMapEnd,pointer(aUpperSearch));
end;

function TSynLogFile.EventDateTime(aIndex: integer): TDateTime;
var TimeStamp: Int64;
begin
  if cardinal(aIndex)>=cardinal(fCount) then
    result := 0 else
    if fFreq=0 then
      Iso8601ToDateTimePUTF8CharVar(fLines[aIndex],17,result) else
      if HexDisplayToBin(fLines[aIndex],@TimeStamp,sizeof(TimeStamp)) then
        result := fStartDateTime+(TimeStamp/fFreqPerDay) else
        result := 0;
end;

procedure TSynLogFile.LoadFromMap(AverageLineLength: integer=32);
  var PBeg, P, PEnd: PUTF8Char;
  function StrPosI(P,PEnd: PUTF8Char; SearchUp: PAnsiChar): PUTF8Char;
  begin
    result := P;
    while result<PEnd do
      if IdemPChar(result,SearchUp) then
        exit else
        inc(result);
    result := nil;
  end;
  function GetOne(const UP: RawUTF8; var S: RawUTF8): boolean;
  var LUP: integer;
  begin
    LUP := length(UP);
    P := StrPosI(PBeg,PEnd-LUP,pointer(UP));
    if P=nil then
      result := false else begin
      SetString(S,PAnsiChar(PBeg),P-PBeg);
      PBeg := P+LUP;
      result := pointer(S)<>nil;
    end;
  end;
  function ComputeProperTime(var procndx: Integer): cardinal; // returns leave
  var start, i: integer;
  begin
    start := procndx;
    with fLogProcNatural[procndx] do begin
      ProperTime := Time;
      result := Index;
    end;
    repeat
      inc(result);
      if result>=Cardinal(Count) then
        break;
      case fLevels[result] of
      sllEnter: begin
        inc(procndx);
        assert(fLogProcNatural[procndx].Index=result);
        result := ComputeProperTime(procndx);
      end;
      sllLeave: begin
        with fLogProcNatural[start] do
        for i := start+1 to procndx do
          dec(ProperTime,fLogProcNatural[i].ProperTime);
        break;
      end;
      end;
    until false;
  end;
  procedure CleanLevels(Log: TSynLogFile);
  var i, aCount, pCount: integer;
  begin
    aCount := 0;
    pCount := 0;
    with Log do
    for i := 0 to fCount-1 do
      if fLevels[i]<>sllNone then begin
        fLevels[aCount] := fLevels[i];
        fLines[aCount] := fLines[i];
        if fThreads<>nil then
          fThreads[aCount] := fThreads[i];
        if fLevels[i]=sllEnter then begin
          fLogProcNatural[pCount].Index := aCount;
          inc(pCount);
        end;
        inc(aCount);
      end;
    Log.fCount := aCount;
    assert(pCount=Log.fLogProcNaturalCount);
  end;
var aWow64: RawUTF8;
    i, j, Level: integer;
    TSEnter, TSLeave: Int64;
    OK: boolean;
begin
  // 1. calculate fLines[] + fCount and fLevels[] + fLogProcNatural[] from .log content
  fLineHeaderCountToIgnore := 3;
  inherited LoadFromMap(100);
  // 2. fast retrieval of header
  OK := false;
  try
{  C:\Dev\lib\SQLite3\exe\TestSQL3.exe 0.0.0.0 (2011-04-07 11:09:06)
   Host=BW013299 User=G018869 CPU=1*0-15-1027 OS=2.3=5.1.2600 Wow64=0 Freq=3579545
   TSynLog 1.13 LVCL 2011-04-07 12:04:09 }
    if (fCount<=fLineHeaderCountToIgnore) or LineSizeSmallerThan(0,24) or
       not IdemPChar(fLines[1],'HOST=') or LineSizeSmallerThan(2,24) or
       (fLevels=nil) or (fLineLevelOffset=0) then
      exit;
    PBeg := fLines[0];
    PEnd := PBeg+LineSize(0)-12;
    if PEnd<PBeg then
      exit;
    if PEnd^='(' then begin  // '(2011-04-07)' format
      if (PEnd[-1]<>' ') or (PEnd[0]<>'(') or (PEnd[11]<>')') then
        exit;
      Iso8601ToDateTimePUTF8CharVar(PEnd+1,10,fExeDate);
    end else begin  // '(2011-04-07 11:09:06)' format
      dec(PEnd,9);
      if (PEnd<PBeg) or (PEnd[-1]<>' ') or (PEnd[0]<>'(') or (PEnd[20]<>')') then
        exit;
      Iso8601ToDateTimePUTF8CharVar(PEnd+1,19,fExeDate);
    end;
    dec(PEnd);
    P := PEnd;
    repeat if P<=PBeg then exit else dec(P) until P^=' ';
    SetString(fExeVersion,PAnsiChar(P+1),PEnd-P-1);
    repeat dec(P); if P<=PBeg then exit; until P^<>' ';
    SetString(fExeName,PAnsiChar(PBeg),P-PBeg+1);
    PBeg := PUTF8Char(fLines[1])+5;
    PEnd := PUTF8Char(fLines[1])+LineSize(1);
    if not GetOne(' USER=',fHost) or not GetOne(' CPU=',fUser) or
       not GetOne(' OS=',fCPU)    or not GetOne(' WOW64=',fOsDetailed) or
       not GetOne(' FREQ=',aWow64) then
      exit;
    {$ifdef MSWINDOWS}
    fWow64 := aWow64='1';
    {$endif}
    SetInt64(PBeg,fFreq);
    if fFreq=0 then
      exit;
    while (PBeg<PEnd) and (PBeg^>' ') do inc(PBeg);
    if IdemPChar(PBeg,' INSTANCE=') then // only available for a library log
      SetString(fInstanceName,PBeg+10,PEnd-PBeg-10);
    fHeaderLinesCount := 4;
    while fHeaderLinesCount<fCount do begin
      if PAnsiChar(fLines[fHeaderLinesCount-1])^<' ' then
        break; // end of header = void line
      inc(fHeaderLinesCount);
    end;
    if (LineSize(fHeaderLinesCount-1)<>0) or
       LineSizeSmallerThan(fHeaderLinesCount,16) then
      exit;
    if fHeaderLinesCount<>4 then
      SetString(fHeaders,PAnsiChar(fLines[2]),
        PtrInt(fLines[fHeaderLinesCount-2])-PtrInt(fLines[2]));
    if PWord(fLines[fHeaderLinesCount])^<>ord('0')+ord('0')shl 8 then // YYYYMMDD -> 20101225 e.g.
      fFreq := 0 else // =0 if date time, >0 if high-resolution time stamp
      fFreqPerDay := fFreq*SecsPerDay;
    Iso8601ToDateTimePUTF8CharVar(PUTF8Char(
      fLines[fHeaderLinesCount-2])+LineSize(fHeaderLinesCount-2)-19,19,fStartDateTime);
    if fStartDateTime=0 then
      exit;
    P := pointer(fOSDetailed);
    {$ifdef MSWINDOWS}
    fOS := TWindowsVersion(GetNextItemCardinal(P,'.'));
    fOSServicePack := GetNextItemCardinal(P);
    {$endif}
    // 3. compute fCount and fLines[] so that all fLevels[]<>sllNone
    CleanLevels(self);
    if Length(fLevels)-fCount>16384 then begin // size down only if worth it
      SetLength(fLevels,fCount);
      if fThreads<>nil then
        SetLength(fThreads,fCount);
    end;
    // 4. compute customer-side profiling
    SetLength(fLogProcNatural,fLogProcNaturalCount);
    for i := 0 to fLogProcNaturalCount-1 do
      if fLogProcNatural[i].Time>=99000000 then begin // overange 99.000.000 -> compute
        Level := 0;
        j := fLogProcNatural[i].Index;
        repeat
          inc(j);
          if j=fCount then break;
          case fLevels[j] of
          sllEnter: inc(Level);
          sllLeave: if Level=0 then begin
            if fFreq=0 then // adjust huge seconds timing from date/time column
              fLogProcNatural[i].Time :=
                Round((EventDateTime(j)-EventDateTime(fLogProcNatural[i].Index))*86400000000.0)+
                fLogProcNatural[i].Time mod 1000000 else begin
              HexDisplayToBin(fLines[fLogProcNatural[i].Index],@TSEnter,sizeof(TSEnter));
              HexDisplayToBin(fLines[j],@TSLeave,sizeof(TSLeave));
              fLogProcNatural[i].Time := ((TSLeave-TSEnter)*(1000*1000)) div fFreq;
            end;
            break;
          end else dec(Level);
          end;
        until false;
      end;
    i := 0;
    while i<fLogProcNaturalCount do begin
      ComputeProperTime(i);
      inc(i);
    end;
    LogProcMerged := false; // set LogProp[]
    OK := true;
  finally
    if not OK then begin
      Finalize(fLevels); // mark not a valid .log
      Finalize(fThreads);
      fLineLevelOffset := 0;
    end;
  end;
end;

procedure TSynLogFile.LogProcSort(Order: TLogProcSortOrder);
begin
  if (fLogProcNaturalCount<=1) or (Order=fLogProcSortInternalOrder) then
    Exit;
  fLogProcSortInternalOrder := Order;
  LogProcSortInternal(0,LogProcCount-1);
end;

function StrICompLeftTrim(Str1, Str2: PUTF8Char): PtrInt;
var C1, C2: integer;
begin
  while Str1^ in [#9,' '] do inc(Str1);
  while Str2^ in [#9,' '] do inc(Str2);
  repeat
    C1 := NormToUpperByte[ord(Str1^)];
    C2 := NormToUpperByte[ord(Str2^)];
    if (C1<>C2) or (C1<32) then
      break;
    Inc(Str1);
    Inc(Str2);
  until false;
  Result := C1-C2;
end;

function TSynLogFile.LogProcSortComp(A, B: Integer): integer;
begin
  case fLogProcSortInternalOrder of
    soByName: result :=
      StrICompLeftTrim(PUTF8Char(fLines[LogProc[A].Index])+fLineTextOffset,
                       PUTF8Char(fLines[LogProc[B].Index])+fLineTextOffset);
    soByOccurrence: result := LogProc[A].Index-LogProc[B].Index;
    soByTime:       result := LogProc[B].Time-LogProc[A].Time;
    soByProperTime: result := LogProc[B].ProperTime-LogProc[A].ProperTime;
    else  result := A-B;
  end;
end;

procedure TSynLogFile.LogProcSortInternal(L, R: integer);
var I,J,P: integer;
begin
  if L<R then
  repeat
    I := L; J := R;
    P := (L + R) shr 1;
    repeat
      while LogProcSortComp(I,P)<0 do inc(I);
      while LogProcSortComp(J,P)>0 do dec(J);
      if I<=J then begin
        Exchg(@LogProc[i],@LogProc[j],sizeof(LogProc[i])-1);
        if P = I then P := J else if P = J then P := I;
        Inc(I); Dec(J);
      end;
    until I>J;
    if L<J then
      LogProcSortInternal(L,J);
     L := I;
  until I>=R;
end;

procedure TSynLogFile.ProcessOneLine(LineBeg, LineEnd: PUTF8Char);
  function DecodeMicroSec(P: PByte): integer;
  var B: integer;
  begin // fast decode 00.020.006 at the end of the line
    B := ConvertHexToBin[P^];   // 00
    if B>9 then
      result := -1 else begin
      result := B;
      inc(P);
      B := ConvertHexToBin[P^];
      if B>9 then
        result := -1 else begin
        result := result*10+B;
        inc(P,2);                 // .
        B := ConvertHexToBin[P^]; // 020
        if B>9 then
          result := -1 else begin
          result := result*10+B;
          inc(P);
          B := ConvertHexToBin[P^];
          if B>9 then
            result := -1 else begin
            result := result*10+B;
            inc(P);
            B := ConvertHexToBin[P^];
            if B>9 then
              result := -1 else begin
              result := result*10+B;
              inc(P,2);                 // .
              B := ConvertHexToBin[P^]; // 006
              if B>9 then
                result := -1 else begin
                result := result*10+B;
                inc(P);
                B := ConvertHexToBin[P^];
                if B>9 then
                  result := -1 else begin
                  result := result*10+B;
                  inc(P);
                  B := ConvertHexToBin[P^];
                  if B>9 then
                    result := -1 else
                    result := result*10+B;
                end;
              end;
            end;
          end;
        end;
      end;
    end;
  end;
var V: cardinal;
    MS: integer;
    L: TSynLogInfo;
begin
  inherited ProcessOneLine(LineBeg,LineEnd);
  if length(fLevels)<fLinesMax then
    SetLength(fLevels,fLinesMax);
  if (fCount<=fLineHeaderCountToIgnore) or (LineEnd-LineBeg<24) then
    exit;
  if fLineLevelOffset=0 then begin
    if (fCount>50) or not (LineBeg[0] in ['0'..'9']) then
      exit; // definitively does not sound like a .log content
    if LineBeg[8]=' ' then // YYYYMMDD HHMMSS is one char bigger than TimeStamp
      fLineLevelOffset := 19 else
      fLineLevelOffset := 18;
    if LineBeg[19]='!' then begin // thread number = 1 -> '  !'
      inc(fLineLevelOffset,3);
      fThreadsCount := fLinesMax;
      SetLength(fThreads,fLinesMax);
    end;
    fLineTextOffset := fLineLevelOffset+4;
  end;
  V := PCardinal(LineBeg+fLineLevelOffset)^;
  for L := succ(sllNone) to high(TSynLogInfo) do
    if V=fLogLevelsTextMap[L] then begin
      fLevels[fCount-1] := L; // need exact match of level text
      include(fLevelUsed,L);
      case L of
      sllEnter: begin
        if Cardinal(fLogProcStackCount)>=Cardinal(length(fLogProcStack)) then
          SetLength(fLogProcStack,length(fLogProcStack)+256);
        fLogProcStack[fLogProcStackCount] := fLogProcNaturalCount;
        inc(fLogProcStackCount);
        if Cardinal(fLogProcNaturalCount)>=Cardinal(length(fLogProcNatural)) then
          SetLength(fLogProcNatural,length(fLogProcNatural)+32768);
        // fLogProcNatural[].Index will be set in TSynLogFile.LoadFromMap
        inc(fLogProcNaturalCount);
      end;
      sllLeave:
      if (LineEnd-LineBeg>10) and (LineEnd[-4]='.') and (LineEnd[-8]='.') and
         (fLogProcStackCount>0) then begin // 00.020.006
        MS := DecodeMicroSec(PByte(LineEnd-10));
        if MS>=0 then begin
          dec(fLogProcStackCount);
          fLogProcNatural[fLogProcStack[fLogProcStackCount]].Time := MS;
        end;
      end;
      end;
      if fThreads<>nil then begin
        if fThreadsCount<fLinesMax then begin
          fThreadsCount := fLinesMax;
          SetLength(fThreads,fLinesMax);
        end;
        V := Chars3ToInt18(LineBeg+fLineLevelOffset-5);
        fThreads[fCount-1] := V;
        if V>fThreadMax then begin
          fThreadMax := V;
          if V>=fThreadsRowsCount then begin
            fThreadsRowsCount := V+256;
            SetLength(fThreadsRows,fThreadsRowsCount);
          end;
        end;
        inc(fThreadsRows[V]);
      end;
      break;
    end;
end;

function TSynLogFile.GetEventText(index: integer): RawUTF8;
var L: cardinal;
begin
  if (self=nil) or (cardinal(index)>=cardinal(fCount)) then
    result := '' else begin
    L := GetLineSize(fLines[index],fMapEnd);
    if L<=fLineTextOffset then
      result := '' else
      SetString(result,PAnsiChar(fLines[index])+fLineTextOffset,L-fLineTextOffset);
  end;
end;

procedure TSynLogFile.SetLogProcMerged(const Value: boolean);
var i: integer;
    P: ^TSynLogFileProc;
    O: TLogProcSortOrder;
begin
  fLogProcIsMerged := Value;
  O := fLogProcSortInternalOrder;
  if Value then begin
    if fLogProcMerged=nil then begin
      fLogProcCurrent := pointer(fLogProcNatural);
      fLogProcCurrentCount := fLogProcNaturalCount;
      LogProcSort(soByName); // sort by name to identify unique
      SetLength(fLogProcMerged,fLogProcNaturalCount);
      fLogProcMergedCount := 0;
      i := 0;
      P := pointer(fLogProcNatural);
      repeat
        with fLogProcMerged[fLogProcMergedCount] do begin
          repeat
            Index := P^.Index;
            inc(Time,P^.Time);
            inc(ProperTime,P^.ProperTime);
            inc(i);
            inc(P);
          until (i>=fLogProcNaturalCount) or
                (StrICompLeftTrim(PUTF8Char(fLines[LogProc[i-1].Index])+22,
                 PUTF8Char(fLines[P^.Index])+22)<>0);
        end;
        inc(fLogProcMergedCount);
      until i>=fLogProcNaturalCount;
      SetLength(fLogProcMerged,fLogProcMergedCount);
    end;
    fLogProcCurrent := pointer(fLogProcMerged);
    fLogProcCurrentCount := fLogProcMergedCount;
  end else begin
    fLogProcCurrent := pointer(fLogProcNatural);
    fLogProcCurrentCount := fLogProcNaturalCount;
  end;
  fLogProcSortInternalOrder := soNone;
  LogProcSort(O); // restore previous sort order
end;


{ TSynTestsLogged }

function TSynTestsLogged.BeforeRun(const TestName: RawUTF8): IUnknown;
begin
  result := TSynLogTestLog.Enter(TObject(nil),pointer(TestName));
end;

constructor TSynTestsLogged.Create(const Ident: string);
begin
  inherited;
  with TSynLogTestLog.Family do begin
    if integer(Level)=0 then // if no exception is set
      Level := [sllException,sllExceptionOS,sllFail];
    {$ifdef MSWINDOWS}
    if AutoFlushTimeOut=0 then
      AutoFlushTimeOut := 2; // flush any pending text into .log file every 2 sec
    {$endif}
    fLogFile := SynLog;
  end;
end;

type
  PTTextWriter = ^TTextWriter;

function SynTestsTextOut(var t: TTextRec): Integer;
begin
  if t.BufPos = 0 then
    Result := 0 else begin
    if FileWrite(t.Handle,t.BufPtr^,t.BufPos)<>integer(t.BufPos) then
      Result := GetLastError else
      Result := 0;
    if PTTextWriter(@t.UserData)^<>nil then
      PTTextWriter(@t.UserData)^.AddJSONEscape(t.BufPtr,t.Bufpos);
    t.BufPos := 0;
  end;
end;

procedure TSynTestsLogged.CreateSaveToFile;
begin
  inherited;
  with TTextRec(fSaveToFile) do begin
    InOutFunc := @SynTestsTextOut;
    FlushFunc := @SynTestsTextOut;
    fConsoleDup := TTextWriter.CreateOwnedStream;
    fConsoleDup.AddShort('{"Msg"="');
    PTTextWriter(@UserData)^ := fConsoleDup;
  end;
end;

destructor TSynTestsLogged.Destroy;
begin
  if (fLogFile.fWriter<>nil) and (fConsoleDup<>nil) then begin
    fConsoleDup.Add('"','}');
    fLogFile.Log(sllCustom1,fConsoleDup.Text);
  end;
  inherited;
  fConsoleDup.Free;
end;

const
 sFailed: PWinAnsiChar = '%: % "%"';

procedure TSynTestsLogged.Failed(const msg: string; aTest: TSynTestCase);
{$ifdef DELPHI5OROLDER}
var tmp: RawUTF8;
{$endif}
begin
  inherited;
  with TestCase[fCurrentMethod] do begin
{$ifdef DELPHI5OROLDER}
    tmp := Ident+': '+TestName[fCurrentMethodIndex];
    if msg<>'' then
      tmp := tmp+' "'+msg+'"';
    fLogFile.Log(sllFail,tmp);
{$else}
    fLogFile.Log(sllFail,sFailed,[Ident,TestName[fCurrentMethodIndex],msg],aTest);
{$endif}
  end;
end;


function EventArchiveDelete(const aOldLogFileName, aDestinationPath: TFileName): boolean;
begin
  result := DeleteFile(aOldLogFileName);
end;

function EventArchiveSynLZ(const aOldLogFileName, aDestinationPath: TFileName): boolean;
begin // aDestinationPath = 'ArchivePath\log\YYYYMM\'
  Result := false;
  if (aOldLogFileName<>'') and FileExists(aOldLogFileName) then
  try
    if DirectoryExists(aDestinationPath) or CreateDir(aDestinationPath) then
      if FileSynLZ(aOldLogFileName,
         aDestinationPath+ExtractFileName(aOldLogFileName)+'.synlz',LOG_MAGIC) then
        result := DeleteFile(aOldLogFileName);
  except
    on Exception do
      result := false;
  end;
end;


{ TRawByteStringStream }

constructor TRawByteStringStream.Create(const aString: RawByteString);
begin
  fDataString := aString;
end;
................................................................................
  if i<0 then
    result := aDefaultValue else
    result := List[i].Value;
end;

function TSynNameValue.Initialized: boolean;
begin
  result := fDynArray.Value=@List;
end;

function TSynNameValue.GetBlobData: RawByteString;
begin
  result := fDynArray.SaveTo;
end;

................................................................................
end;

procedure GarbageCollectorFreeAndNil(var InstanceVariable; Instance: TObject);
begin
  TObject(InstanceVariable) := Instance;
  GarbageCollectorFreeAndNilList.Add(@InstanceVariable);
end;





















initialization
  // initialization of global variables
  GarbageCollectorFreeAndNilList := TList.Create;
  GarbageCollectorFreeAndNil(GarbageCollector,TObjectList.Create);
  {$ifndef FPC}
  {$ifndef CPUARM}







|







 







>







 







>
>
>







 







<
<
<
<
<
<
<
<
<







 







|







 







<
<
<








<







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







|

<
<
<







 







<
<
<







 







>
>
>

<







 







>
>
>
>
>
>
>
>







 







|







 







|







 







>







 







>
>
|



>
>







 







|







 







>
>







 







>
>
>
>
>
>







 







>
>
>
>
>
>
>







 







>
>







 







>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







|






>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|







 







>



<







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







>
>
>
>







 







<
<
<
<
<
<
<
<
<
<
<







 







|







 







|
|







 







<
<
<







 







<
<
<







 







<
<
<
<
<
<
<
<
<













<







 







<
<
<







 







|









|







 







|









|









|
>
>
>
|

<
<
<
|
|
|
<
>
|
|
|
|
|
|
|
|
|
<
>
|
|
<
|
<
|
>
>
|
|
|
|
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







|


|





|








|




|





|












|





|







 







|








|


|




|

|



|







 







|







 







|







 







|







 







|







 







|









|







 







|
>
>
>
>
>







 







|







 







|


|







 







|
|
|
|
|
|
|
|
|
|
|
|
|
|
|







 







|







 







|







 







|







 







|







 







|







 







|







 







|

|











|






|







 







|
|







 







|




|







 







|







 







|




|







 







|

|



|







 







|

|







 







|







|






|




|







 







|
|





|







 







|




|
|







 







|









|











|







 







|







 







|

|







 







|











|







 







|







 







|






|







 







|




|




|







 







|







 







|







 







|







 







>
>
>
>
>







 







>
>
>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







<
<
<
<
<







 







>
>
>
>
>
>
>
>
>
>
>







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<








<
|
>



|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<

|
<
<
<

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







|







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
...
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
...
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
...
411
412
413
414
415
416
417









418
419
420
421
422
423
424
...
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
...
464
465
466
467
468
469
470



471
472
473
474
475
476
477
478

479
480
481
482
483
484
485
...
522
523
524
525
526
527
528






























529
530
531
532
533
534
535
...
557
558
559
560
561
562
563
564
565



566
567
568
569
570
571
572
...
593
594
595
596
597
598
599



600
601
602
603
604
605
606
...
611
612
613
614
615
616
617
618
619
620
621

622
623
624
625
626
627
628
....
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
....
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
....
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
....
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
....
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
....
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881
3882
....
5189
5190
5191
5192
5193
5194
5195
5196
5197
5198
5199
5200
5201
5202
5203
5204
....
5625
5626
5627
5628
5629
5630
5631
5632
5633
5634
5635
5636
5637
5638
5639
5640
5641
5642
5643
5644
....
6340
6341
6342
6343
6344
6345
6346
6347
6348
6349
6350
6351
6352
6353
6354
6355
6356
6357
6358
6359
6360
....
6481
6482
6483
6484
6485
6486
6487
6488
6489
6490
6491
6492
6493
6494
6495
6496
....
6780
6781
6782
6783
6784
6785
6786
6787
6788
6789
6790
6791
6792
6793
6794
....
7086
7087
7088
7089
7090
7091
7092
7093
7094
7095
7096
7097
7098
7099
7100
7101
7102
7103
7104
7105
7106
7107
7108
7109
7110
7111
7112
7113
7114
7115
7116
7117
7118
7119
7120
7121
7122
7123
7124
7125
7126
7127
7128
7129
7130
7131
7132
7133
7134
7135
7136
7137
7138
7139
7140
7141
7142
7143
7144
7145
7146
7147
7148
7149
7150
7151
7152
7153
7154
7155
7156
7157
7158
7159
7160
....
8380
8381
8382
8383
8384
8385
8386























8387
8388
8389
8390
8391
8392
8393
.....
10369
10370
10371
10372
10373
10374
10375
10376
10377
10378
10379
10380
10381
10382
10383
10384
10385
10386
10387
10388
10389
10390
10391
10392
10393
10394
10395
10396
10397
10398
10399
10400
10401
10402
10403
10404
10405
10406
10407
10408
10409
10410
10411
10412
10413
10414
10415
10416
10417
10418
10419
10420
10421
.....
10502
10503
10504
10505
10506
10507
10508
10509
10510
10511
10512

10513
10514
10515
10516
10517
10518
10519
.....
10769
10770
10771
10772
10773
10774
10775

























































































































































































































































































10776
10777
10778
10779
10780
10781
10782
.....
10790
10791
10792
10793
10794
10795
10796













































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































10797
10798
10799
10800
10801
10802
10803
.....
10805
10806
10807
10808
10809
10810
10811
10812
10813
10814
10815
10816
10817
10818
10819
10820
10821
10822
.....
10841
10842
10843
10844
10845
10846
10847











10848
10849
10850
10851
10852
10853
10854
.....
10863
10864
10865
10866
10867
10868
10869
10870
10871
10872
10873
10874
10875
10876
10877
.....
10956
10957
10958
10959
10960
10961
10962
10963
10964
10965
10966
10967
10968
10969
10970
10971
.....
15754
15755
15756
15757
15758
15759
15760



15761
15762
15763
15764
15765
15766
15767
.....
15769
15770
15771
15772
15773
15774
15775



15776
15777
15778
15779
15780
15781
15782
.....
15790
15791
15792
15793
15794
15795
15796









15797
15798
15799
15800
15801
15802
15803
15804
15805
15806
15807
15808
15809

15810
15811
15812
15813
15814
15815
15816
.....
17443
17444
17445
17446
17447
17448
17449



17450
17451
17452
17453
17454
17455
17456
.....
23486
23487
23488
23489
23490
23491
23492
23493
23494
23495
23496
23497
23498
23499
23500
23501
23502
23503
23504
23505
23506
23507
23508
23509
23510
.....
23529
23530
23531
23532
23533
23534
23535
23536
23537
23538
23539
23540
23541
23542
23543
23544
23545
23546
23547
23548
23549
23550
23551
23552
23553
23554
23555
23556
23557
23558
23559
23560
23561



23562
23563
23564

23565
23566
23567
23568
23569
23570
23571
23572
23573
23574

23575
23576
23577

23578

23579
23580
23581
23582
23583
23584
23585
23586














23587
23588


















23589
23590
23591
23592
23593
23594
23595
.....
23639
23640
23641
23642
23643
23644
23645















23646
23647
23648
23649
23650
23651
23652
.....
29698
29699
29700
29701
29702
29703
29704
29705
29706
29707
29708
29709
29710
29711
29712
29713
29714
29715
29716
29717
29718
29719
29720
29721
29722
29723
29724
29725
29726
29727
29728
29729
29730
29731
29732
29733
29734
29735
29736
29737
29738
29739
29740
29741
29742
29743
29744
29745
29746
29747
29748
29749
29750
29751
29752
29753
29754
29755
29756
29757
29758
29759
29760
.....
29761
29762
29763
29764
29765
29766
29767
29768
29769
29770
29771
29772
29773
29774
29775
29776
29777
29778
29779
29780
29781
29782
29783
29784
29785
29786
29787
29788
29789
29790
29791
29792
29793
29794
29795
29796
29797
29798
.....
29811
29812
29813
29814
29815
29816
29817
29818
29819
29820
29821
29822
29823
29824
29825
.....
29864
29865
29866
29867
29868
29869
29870
29871
29872
29873
29874
29875
29876
29877
29878
.....
29892
29893
29894
29895
29896
29897
29898
29899
29900
29901
29902
29903
29904
29905
29906
.....
29912
29913
29914
29915
29916
29917
29918
29919
29920
29921
29922
29923
29924
29925
29926
.....
29980
29981
29982
29983
29984
29985
29986
29987
29988
29989
29990
29991
29992
29993
29994
29995
29996
29997
29998
29999
30000
30001
30002
30003
30004
.....
30106
30107
30108
30109
30110
30111
30112
30113
30114
30115
30116
30117
30118
30119
30120
30121
30122
30123
30124
30125
.....
30223
30224
30225
30226
30227
30228
30229
30230
30231
30232
30233
30234
30235
30236
30237
.....
30259
30260
30261
30262
30263
30264
30265
30266
30267
30268
30269
30270
30271
30272
30273
30274
30275
30276
.....
30279
30280
30281
30282
30283
30284
30285
30286
30287
30288
30289
30290
30291
30292
30293
30294
30295
30296
30297
30298
30299
30300
30301
30302
30303
30304
30305
30306
30307
.....
30381
30382
30383
30384
30385
30386
30387
30388
30389
30390
30391
30392
30393
30394
30395
.....
30401
30402
30403
30404
30405
30406
30407
30408
30409
30410
30411
30412
30413
30414
30415
.....
30479
30480
30481
30482
30483
30484
30485
30486
30487
30488
30489
30490
30491
30492
30493
.....
30517
30518
30519
30520
30521
30522
30523
30524
30525
30526
30527
30528
30529
30530
30531
.....
30533
30534
30535
30536
30537
30538
30539
30540
30541
30542
30543
30544
30545
30546
30547
.....
30551
30552
30553
30554
30555
30556
30557
30558
30559
30560
30561
30562
30563
30564
30565
.....
30678
30679
30680
30681
30682
30683
30684
30685
30686
30687
30688
30689
30690
30691
30692
30693
30694
30695
30696
30697
30698
30699
30700
30701
30702
30703
30704
30705
30706
30707
30708
30709
30710
30711
30712
30713
.....
30750
30751
30752
30753
30754
30755
30756
30757
30758
30759
30760
30761
30762
30763
30764
30765
.....
30801
30802
30803
30804
30805
30806
30807
30808
30809
30810
30811
30812
30813
30814
30815
30816
30817
30818
30819
30820
.....
30856
30857
30858
30859
30860
30861
30862
30863
30864
30865
30866
30867
30868
30869
30870
.....
30901
30902
30903
30904
30905
30906
30907
30908
30909
30910
30911
30912
30913
30914
30915
30916
30917
30918
30919
30920
.....
30943
30944
30945
30946
30947
30948
30949
30950
30951
30952
30953
30954
30955
30956
30957
30958
30959
30960
30961
30962
30963
.....
30974
30975
30976
30977
30978
30979
30980
30981
30982
30983
30984
30985
30986
30987
30988
30989
30990
.....
30991
30992
30993
30994
30995
30996
30997
30998
30999
31000
31001
31002
31003
31004
31005
31006
31007
31008
31009
31010
31011
31012
31013
31014
31015
31016
31017
31018
31019
31020
31021
31022
31023
31024
31025
.....
31033
31034
31035
31036
31037
31038
31039
31040
31041
31042
31043
31044
31045
31046
31047
31048
31049
31050
31051
31052
31053
31054
.....
31059
31060
31061
31062
31063
31064
31065
31066
31067
31068
31069
31070
31071
31072
31073
31074
31075
31076
31077
31078
31079
.....
31081
31082
31083
31084
31085
31086
31087
31088
31089
31090
31091
31092
31093
31094
31095
31096
31097
31098
31099
31100
31101
31102
31103
31104
31105
31106
31107
31108
31109
31110
31111
31112
31113
31114
31115
31116
31117
.....
31118
31119
31120
31121
31122
31123
31124
31125
31126
31127
31128
31129
31130
31131
31132
.....
31300
31301
31302
31303
31304
31305
31306
31307
31308
31309
31310
31311
31312
31313
31314
31315
31316
.....
31414
31415
31416
31417
31418
31419
31420
31421
31422
31423
31424
31425
31426
31427
31428
31429
31430
31431
31432
31433
31434
31435
31436
31437
31438
31439
31440
.....
31441
31442
31443
31444
31445
31446
31447
31448
31449
31450
31451
31452
31453
31454
31455
.....
31460
31461
31462
31463
31464
31465
31466
31467
31468
31469
31470
31471
31472
31473
31474
31475
31476
31477
31478
31479
31480
31481
.....
31685
31686
31687
31688
31689
31690
31691
31692
31693
31694
31695
31696
31697
31698
31699
31700
31701
31702
31703
31704
31705
31706
31707
31708
31709
.....
31725
31726
31727
31728
31729
31730
31731
31732
31733
31734
31735
31736
31737
31738
31739
.....
31755
31756
31757
31758
31759
31760
31761
31762
31763
31764
31765
31766
31767
31768
31769
.....
32793
32794
32795
32796
32797
32798
32799
32800
32801
32802
32803
32804
32805
32806
32807
.....
33815
33816
33817
33818
33819
33820
33821
33822
33823
33824
33825
33826
33827
33828
33829
33830
33831
33832
33833
.....
34014
34015
34016
34017
34018
34019
34020
34021
34022
34023
34024
34025
34026
34027
34028
34029
34030
34031
34032
.....
35464
35465
35466
35467
35468
35469
35470
35471
35472
35473
35474
35475
35476
35477
35478
35479
35480
35481
35482
35483
35484
35485
35486
35487
35488
35489
35490
35491
35492
35493
35494
35495
35496
35497
35498
35499
35500
35501
35502
35503
35504
35505
35506
35507
35508
35509
35510
35511
35512
35513
35514
35515
35516
35517
35518
35519
35520
35521
35522
35523
35524
35525
35526
35527
35528
35529
35530
35531
35532
35533
35534
35535
35536
35537
35538
35539
35540
35541
35542
35543
35544
35545
35546
35547
35548
35549
35550
35551
35552
35553
35554
35555
35556
35557
35558
35559
35560
35561
35562
35563
35564
35565
35566
35567
35568
35569
35570
35571
35572
35573
35574
35575
35576
35577
35578
35579
35580
35581
35582
35583
35584
.....
36090
36091
36092
36093
36094
36095
36096






































































































































































































































































































































































































































































































































































































36097
36098
36099
36100
36101
36102
36103
.....
37621
37622
37623
37624
37625
37626
37627
37628
37629
37630
37631
37632
37633
37634
37635
37636
37637
37638
37639
37640
37641
37642
37643
37644
37645
37646
37647
37648
37649
37650
37651
37652
37653
37654
.....
40427
40428
40429
40430
40431
40432
40433





40434
40435
40436
40437
40438
40439
40440
.....
40441
40442
40443
40444
40445
40446
40447
40448
40449
40450
40451
40452
40453
40454
40455
40456
40457
40458
40459
40460
40461
40462
40463
40464
40465
.....
40755
40756
40757
40758
40759
40760
40761
























40762













































































































































































































































































































































































































































































































































































































40763
40764
40765
40766
40767
40768
40769
40770

40771
40772
40773
40774
40775
40776





























































































































































































































































40777



















































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































40778
40779



40780



































































































































40781
40782
40783
40784
40785
40786
40787
.....
40854
40855
40856
40857
40858
40859
40860
40861
40862
40863
40864
40865
40866
40867
40868
.....
40996
40997
40998
40999
41000
41001
41002

























































































































































































































































































































































































































































































































































































41003
41004
41005
41006
41007
41008
41009
.....
41136
41137
41138
41139
41140
41141
41142
41143
41144
41145
41146
41147
41148
41149
41150
.....
41542
41543
41544
41545
41546
41547
41548
41549
41550
41551
41552
41553
41554
41555
41556
41557
41558
41559
41560
41561
41562
41563
41564
41565
41566
41567
41568
41569
41570
41571
41572
41573
41574
41575
  - fixed implementation issue in function FindNextUTF8WordBegin()
  - fixed false negative issue in TSynSoundEx.UTF8 and TSynSoundEx.Ansi
  - fixed wrong UTF-8 encoding of U+FFF0 used for JSON_BASE64_MAGIC
  - added an optional parameter to StrToCurr64() function, able to return
    a true Int64 value if no decimal is supplied within the input text buffer
  - enhanced TSynAnsiFixedWidth.UnicodeBufferToAnsi average process speed
  - TSynCache.Reset now returns a boolean stating if something was flushed
  - new SynUnicodeToUtf8(), ShortStringToUTF8(), StringToSynUnicode(),
    SynUnicodeToString() functions
  - new StrToCurrency() wrapper function
  - new IdemPropName() overloaded function with two PUTF8Char arguments
  - new UTF8UpperCopy() and UTF8UpperCopy255() optimized functions
  - new GotoNextNotSpace() and GotoEndOfQuotedString() functions
  - new TMemoryMap.Map() method expecting a file name as parameter
  - new TRawUTF8List.LoadFromFile() method
................................................................................
    even after true/false/null, as expected by the specification
  - fixed potential Integer Overflow error in Iso8601ToDateTimePUTF8Char*()
  - added PatchCode() and RedirectCodeRestore() procedures, and some code
    refactoring about process in-memory code patching
  - internal FillChar() will now use faster SSE2 instructions on supported CPUs

  Version 1.18
  - BREAKING CHANGE: SynLog.pas and SynTests.pas were extracted from SynCommons
  - BREAKING CHANGE of TTextWriter.WriteObject() method: serialization is now
    defined with a new TTextWriterWriteObjectOptions set
  - BREAKING CHANGE rename of Iso8601 low-level structure as TTimeLogBits, to use
    explicitly the TTimeLog type and name for all Int64 bit-oriented functions -
    now "Iso8601" naming will be only for standard ISO-8601 text, not Int64 value
  - Delphi XE4/XE5/XE6/XE7 compatibility (Windows target platform only)
  - unit fixed and tested with Delphi XE2 (and up) 64-bit compiler under Windows
................................................................................
    UTF8ToString(aVariant) if you want to use the value with the VCL
  - introducing TDocVariant for variant-based process of any hierarchy
    of objects and/or arrays, with late binding optimized access and JSON
    serialization/unserialization (will also be used for BSON documents storage)
  - UTF-8 process will now handle UTF-16 surrogates - see ticket [4a0382367d] -
    UnicodeCharToUTF8/NextUTF8Char are renamed WideCharToUTF8/NextUTF8UCS4 and
    new UTF16CharToUTF8/UCS4ToUTF8 functions have been introduced
  - added TextColor() and TextBackground() functions - will initialize internal
    console process after any manual AllocConsole call
  - added ConsoleWaitForEnterKey function, able to handle Synchronize() calls
  - StrLen() function will now use faster SSE2 instructions on supported CPUs
  - introduced StrLenPas() function, to be used when buffer is protected
  - included Windows-1258 code page to be recognized as a fixed-width charset
  - TSynAnsiFixedWidth.Create(CODEPAGE_US) will now use a hard-coded table, 
    since some Russian system do tweak the registry to force 1252 page maps 1251
  - introducing TSynAnsiUTF8/TSynAnsiUTF16 to handle CP_UTF8/CP_UTF16 codepages
  - added UTF8AnsiConvert instance, and let TSynAnsiConvert.Engine(0) return
................................................................................
  - introducing TObjectDynArrayWrapper class and IObjectDynArray interface
  - introducing TSynAuthentication class for simple generic authentication
  - added TDynArrayHashed.HashElement property
  - new TDynArrayHashed.AddUniqueName() method
  - introduced TSingleDynArray, recognized as such in JSON serialization
  - added WordScanIndex() and swap32() functions
  - speed improvement of IdemPropNameU() function, with new overload function









  - now FileSize() function won't raise any exception if the file does not exist
    and will return any size > 2 GB as expected
  - faster PosEx() function in pure pascal mode (based on Avatar Zondertau work)
  - added StringDynArrayToRawUTF8DynArray() and StringListToRawUTF8DynArray()
  - added CSVToRawUTF8DynArray() overloaded functions
  - added GetLastCSVItem() function and dedicated HashPointer() function
  - added DirectoryDelete() and EnsureDirectoryExists() function
................................................................................
  - added faster TTextWriter.SetText() method in conjuction to Text function
  - added TTextWriter.Add(const guid: TGUID) overloaded method
  - TTextWriter.Add(Format..) will now ignore any character afer |, i.e. |$ = $
  - added TTextWriter.AddQuotedStr() and AddStringCopy() methods
  - added TTextWriter.AddVoidRecordJSON() method
  - added TTextWriter.AddJSONEscapeAnsiString() method
  - added TTextWriter.AddAnyAnsiString() method and AnyAnsiToUTF8() function
  - added TTextWriter.EndOfLineCRLF property
  - for Delphi 2010 and up, RecordSaveJSON/RecordLoadJSON will use enhanced RTTI
  - before Delphi 2010, you can specify the record layout as text to
    TTextWriter.RegisterCustomJSONSerializerFromText() for JSON serialization
  - added TTextWriter.RegisterCustomJSONSerializerSetOptions() for [da22968223]
  - added TTextWriter.AddDynArrayJSON() overloaded method and new functions
    DynArrayLoadJSON() and DynArraySaveJSON() to be used e.g. for custom
    record JSON serialization, using TDynArrayJSONCustomReader/Writer
................................................................................
    callbacks and/or RegisterCustomJSONSerializerFromText(), or enhanced RTTI
  - added TTextWriter.UnRegisterCustomJSONSerializer() method
  - added TTextWriter.RegisterCustomJSONSerializerFromTextSimpleType() method
  - added TTextWriter.AddTypedJSON() and AddCRAndIdent methods
  - added TJSONWriter.EndJSONObject() method, for writing an optional
    ',"rowCount":' field in non expanded mode - used for all JSON creation
  - added TTextWriter.EchoAdd() and EchoRemove() methods



  - added QuickSortIndexedPUTF8Char() and FastFindIndexedPUTF8Char()
  - added overloaded QuickSortInteger() for synchronous sort of two arrays
  - added GetNextItem64() Int64Scan() Int64ScanExists() QuickSortInt64()
    FastFindInt64Sorted() AddInt64() CSVToInt64DynArray() Int64DynArrayToCSV()
    and VariantToInt64() functions (used during TID=Int64 introduction in ORM)
  - added RawUnicodeToUtf8() and UTF8ToSynUnicode() overloaded procedures
  - added UrlDecodeNextValue() and UrlDecodeNextNameValue() functions
  - added Utf8DecodeToRawUnicodeUI() overloaded function returning text as var

  - added UrlEncodeJsonObject() and new overloaded JSONDecode() function
  - added TRawUTF8DynArrayFrom(const Values: array of RawUTF8) function
  - added overloaded function FindRawUTF8() using array of RawUTF8 parameter
  - added TPropNameList record/object to maintain a stack-based list of names
  - speeed enhancement for TRawUTF8List.Add()
  - added optional aOwnObjects parameter to TRawUTF8List.Create() constructor
  - new TRawUTF8List.GetObjectByName() method
................................................................................
  - now TFileBufferReader.Read() allows forward reading when Data=nil
  - added RecordSaveJSON() function which follows TTextWriter.AddRecordJSON() format
  - added TSynNameValue.InitFromIniSection() method and optional default value
    parameter to TSynNameValue.Value()
  - added TSynNameValue.Delete() and SetBlobDataPtr() methods
  - added TSynNameValue.OnAfterAdd callback event
  - added TObjectListLocked class






























  - expose all internal Hash*() functions (following TDynArrayHashOne prototype)
    in interface section of the unit
  - added crc32c() function using either optimized unrolled version, or SSE 4.2
    instruction: crc32cfast() is 1.7 GB/s, crc32csse42() is 3.7 GB/s
  - added fnv32() function, slower than kr32, but with less collisions
  - added SynLZCompress/SynLZDecompress functions, using crc32c() for hashing
  - added SymmetricEncrypt() function
................................................................................
    (WideString aka OleStr do store their length in bytes, not WideChars)
  - fixed TDynArray.AddArray() method when Count parameter is not specified
  - fixed ticket [ad55566b10] about JSON string escape parsing
  - fixed ticket [cce54e98ca], [388c2768b6] and [355249a9d1] about overflow in
    TTextWriter.AddJSONEscapeW()
  - fixed ticket [a75c0c6759] about TTextWriter.AddNoJSONEscapeW()
  - added TTextWriter.AddHtmlEscape() and TTextWriter.AddXmlEscape() methods
  - new TTextWriter.AddHtmlEscapeWiki() method, supporting wiki-like syntax
  - TTextWriter.AddJSONEscape/AddJSONEscapeW methods speed up



  - fixed ticket [01408fd389] in TRawUTF8List.GetText()
  - fixed ticket [e3ae1005dc] about potential GPF in TRawUTF8List.Delete()
  - fixed ticket [1c940a4437] to avoid negative value in TPrecisionTimer.PerSec,
    in case of incorrect Start/Stop methods sequence
  - implement ticket [e3f9742865] for enhanced JSON in soWriteHumanReadable mode
  - added TPrecisionTimer.ProfileCurrentMethod() and TimeInUniSec property
    for feature request [1abca090ee]
................................................................................
  - added function GetJSONFieldOrObjectOrArray() in unit's interface section  
  - function GotoNextJSONField() renamed GotoNextJSONItem(), and fixed to
    handle nested JSON array or objects in addition to string/numbers
  - added GotoEndJSONItem() and GetJSONItemAsRawJSON() functions
  - added function JSONRetrieveIDField() for fast retrieval of a "ID":.. value
  - added function JSONRetrieveStringField() for retrieval of a string field
    name or value from JSON buffer



  - added PtrUIntScanIndex() and UnixTimeToDateTime/DateTimeToUnixTime()
    UnixMSTimeToDateTime/DateTimeToUnixMSTime functions
  - fixed ticket [aff1352239] to identify 9999-12-31 dates as valid
  - added Iso8601ToTimePUTF8Char[Var]() and IntervalTextToDateTime[Var]() functions
  - added DateTimeToIso8601ExpandedPChar() and Iso8601CheckAndDecode() functions
  - added TTimeLogBits.FromUTCTime method and NowUTC / TimeLogNowUTC functions
  - added TTimeLogBits.FromUnixTime/FromUnixMSTime/ToUnixTime/ToUnixMSTime
................................................................................
  - fixed potential random GPF in TTextWriter after Flush - see [577ad95cfd0]
  - added TTextWriter.Add(const Values: array of const) method
  - added JSONToXML() JSONBufferToXML() and TTextWriter.JSONBufferToXML()
    for direct and fast conversion of any JSON into the corresponding <XML>
  - added JSONReformat() JSONBufferReformat() and TTextWriter.AddJSONReformat()
    for fast conversion into more readable, compact or extended layout 
  - fixed potential GPF issue in TMemoryMapText.LoadFromMap()
  - added TMemoryMapText.AddInMemoryLine method to allow runtime appending of
    new lines of text - used e.g. by TSynLogFile for life update of remote logs
  - added TMemoryMapText.SaveToFile() and TMemoryMapText.SaveToStream() methods
  - allow file size of 0 byte in TMemoryMap.Map()

  - introduced TSynInvokeableVariantType.Clear() and Copy() default methods
  - added TSynInvokeableVariantType.CopyByValue() virtual method
  - added TSynInvokeableVariantType.IsOfType() method
  - TSynInvokeableVariantType.SetProperty() will now convert any varOleStr into
    a RawUTF8/varString, and dereference any simple varByRef transmitted values
    so that we could safely use late-binding with any kind of value
  - internal DispInvoke() function speed-up by caching the latest accessed type
................................................................................

/// return true if up^ is contained inside the UTF-8 buffer p^
// - search up^ at the beginning of every UTF-8 word (aka in Soundex)
// - here a "word" is a Win-Ansi word, i.e. '0'..'9', 'A'..'Z'
// - up^ must be already Upper
function ContainsUTF8(p, up: PUTF8Char): boolean;

const
  /// used e.g. by inlined function GetLineContains()
  ANSICHARNOT01310: set of AnsiChar = [#1..#9,#11,#12,#14..#255];

/// returns TRUE if the supplied uppercased text is contained in the text buffer
function GetLineContains(p,pEnd, up: PUTF8Char): boolean;
  {$ifdef HASINLINE}inline;{$endif}

/// copy source into dest^ with 7 bits upper case conversion
// - returns final dest pointer
// - will copy up to 255 AnsiChar (expect the dest buffer to be array[byte] of
// AnsiChar)
function UpperCopy255(dest: PAnsiChar; const source: RawUTF8): PAnsiChar;

/// copy source into dest^ with WinAnsi 8 bits upper case conversion
................................................................................
function AnsiICompW(u1, u2: PWideChar): PtrInt;

/// SameText() overloaded function with proper UTF-8 decoding
// - fast version using NormToUpper[] array for all Win-Ansi characters
// - this version will decode each UTF-8 glyph before using NormToUpper[]
// - current implementation handles UTF-16 surrogates as UTF8IComp()
function SameTextU(const S1, S2: RawUTF8): Boolean;
  {$ifdef HASINLINE}inline;{$endif}

/// fast conversion of the supplied text into 8 bit uppercase
// - this will not only convert 'a'..'z' into 'A'..'Z', but also accentuated
// latin characters ('e' acute into 'E' e.g.), using NormToUpper[] array
// - it will decode the supplied UTF-8 content to handle more than
// 7 bit of ascii characters (so this function is dedicated to WinAnsi code page
// 1252 characters set)
................................................................................
  {$ifdef UNDIRECTDYNARRAY}
  TDynArray = record
  private
  {$else}
  TDynArray = object
  protected
  {$endif}
    fValue: PPointer;
    fTypeInfo: pointer;
    fElemSize: PtrUInt;
    fElemType: pointer;
    fCompare: TDynArraySortCompare;
    fCountP: PInteger;
    fSorted: boolean;
    fKnownType: TDynArrayKind;
................................................................................
    function GetCount: integer; {$ifdef HASINLINE}inline;{$endif}
    procedure SetCount(aCount: integer);
    function GetCapacity: integer;
    procedure SetCapacity(aCapacity: integer);
    procedure SetCompare(const aCompare: TDynArraySortCompare); {$ifdef HASINLINE}inline;{$endif}
    function FindIndex(const Elem; aIndex: PIntegerDynArray;
      aCompare: TDynArraySortCompare): integer;
    function GetArrayTypeName: RawUTF8;
    /// will set fKnownType and fKnownOffset/fKnownSize fields
    function ToKnownType: TDynArrayKind;
    /// faster equivalency of System.DynArraySetLength() function
    procedure InternalSetLength(NewLength: PtrUInt);
  public
    /// initialize the wrapper with a one-dimension dynamic array
    // - the dynamic array must have been defined with its own type
................................................................................
    // the compare function
    // - Add/Delete/Insert/Load* methods will reset this property to false
    // - Sort method will set this property to true
    // - you MUST set this property to false if you modify the dynamic array
    // content in your code, so that Find() won't try to use binary search in
    // an usorted array, and miss its purpose
    property Sorted: boolean read fSorted write fSorted;
    /// low-level direct access to the storage variable
    property Value: PPointer read fValue;
    /// the known type, possibly retrieved from dynamic array RTTI
    property KnownType: TDynArrayKind read fKnownType;
    /// the known RTTI information of the whole array
    property ArrayType: pointer read fTypeInfo;
    /// the known type name of the whole array
    property ArrayTypeName: RawUTF8 read GetArrayTypeName;
    /// the internal in-memory size of one element, as retrieved from RTTI
    property ElemSize: PtrUInt read fElemSize;
    /// the internal type information of one element, as retrieved from RTTI
    property ElemType: pointer read fElemType;
  end;

  /// function prototype to be used for hashing of an element
................................................................................
  private
    procedure SetCount(aCount: Integer);        inline;
    procedure SetCapacity(aCapacity: Integer);  inline;
    function GetCapacity: Integer;              inline;
  public
    InternalDynArray: TDynArray;
    function Count: Integer;            inline;
    function fValue: PPointer;          inline;
    function ElemSize: PtrUInt;         inline;
    function ElemType: Pointer;         inline;
    function KnownType: TDynArrayKind;  inline;
    procedure Clear;                    inline;
    // warning: you shall call ReHash() after manual Add/Delete
    function Add(const Elem): integer;  inline;
    procedure Delete(aIndex: Integer);  inline;
................................................................................
    procedure Flush; virtual;
    /// add a callback to echo each line written by this class
    // - this class expects AddEndOfLine to mark the end of each line
    procedure EchoAdd(const aEcho: TOnTextWriterEcho);
    /// remove a callback to echo each line written by this class
    // - event should have been previously registered by a EchoAdd() call
    procedure EchoRemove(const aEcho: TOnTextWriterEcho);
    /// reset the internal buffer used for echoing content 
    procedure EchoReset;

    /// append one char to the buffer
    procedure Add(c: AnsiChar); overload;
      {$ifdef HASINLINE}inline;{$endif}
    /// append two chars to the buffer
    procedure Add(c1,c2: AnsiChar); overload;
      {$ifdef HASINLINE}inline;{$endif}
................................................................................
    // WriteObject method for full RTTI handling
    // - default implementation will write TList/TCollection/TStrings/TRawUTF8List
    // as appropriate array of class name/pointer (if woFullExpand is set)
    procedure WriteObject(Value: TObject;
      Options: TTextWriterWriteObjectOptions=[woDontStoreDefault]); virtual;
    /// return the last char appended
    function LastChar: AnsiChar;
    /// how many bytes are currently in the internal buffer and not on disk
    function PendingBytes: PtrUInt;
      {$ifdef HASINLINE}inline;{$endif}
    /// how many bytes were currently written on disk
    // - excluding the bytes in the internal buffer
    property WrittenBytes: cardinal read fTotalFileSize;
    /// the last char appended is canceled
    procedure CancelLastChar;
      {$ifdef HASINLINE}inline;{$endif}
    /// the last char appended is canceled if it was a ','
    procedure CancelLastComma;
      {$ifdef HASINLINE}inline;{$endif}
    /// rewind the Stream to the position when Create() was called
................................................................................
    // - if StoreObjectsAsVarUInt32 is TRUE, all Objects[] properties will be
    // stored as VarUInt32
    procedure WriteRawUTF8List(List: TRawUTF8List; StoreObjectsAsVarUInt32: Boolean=false);
    /// append a TStream content
    // - is StreamSize is left as -1, the Stream.Size is used
    // - the size of the content is stored in the resulting stream
    procedure WriteStream(aStream: TCustomMemoryStream; aStreamSize: Integer=-1);
    /// allows to write directly to a memory buffer
    // - caller should specify the maximum possible number of bytes to be written
    // - then write the data to the returned pointer, and call WriteDirectEnd
    function WriteDirectStart(maxSize: integer; const TooBigMessage: RawUTF8=''): PByte;
    /// finalize a direct write to a memory buffer
    // - by specifying the number of bytes written to the buffer
    procedure WriteDirectEnd(realSize: integer);
    /// write any pending data in the internal buffer to the file
    // - after a Flush, it's possible to call FileSeek64(aFile,....)
    // - returns the number of bytes written between two FLush method calls
    function Flush: Int64; 
    /// rewind the Stream to the position when Create() was called
    // - note that this does not clear the Stream content itself, just
    // move back its writing position to its initial place
................................................................................
    /// retrieve the current in-memory position
    // - if file was not memory-mapped, returns -1
    function CurrentPosition: integer;
    /// raise an exception in case of invalid content
    procedure ErrorInvalidContent;
    /// read-only access to the global file size
    property FileSize: Int64 read fMap.fFileSize;
    /// read-only access to the global mapped buffer binary
    property MappedBuffer: PAnsiChar read fMap.fBuf;
  end;


/// FileSeek() overloaded function, working with huge files
// - Delphi FileSeek() is buggy -> use this function to safe access files > 2 GB
// (thanks to sanyin for the report)
function FileSeek64(Handle: THandle; const Offset: Int64; Origin: cardinal): Int64;
................................................................................
const
  /// map a PtrInt type to the TJSONCustomParserRTTIType set
  ptPtrInt  = {$ifdef CPU64}ptInt64{$else}ptInteger{$endif};
  /// map a PtrUInt type to the TJSONCustomParserRTTIType set
  ptPtrUInt = {$ifdef CPU64}ptInt64{$else}ptCardinal{$endif};
  /// which TJSONCustomParserRTTIType types are not simple types
  PT_COMPLEXTYPES = [ptArray, ptRecord, ptCustom];


{ ************ filtering and validation classes and functions }

/// return TRUE if the supplied content is a valid email address
// - follows RFC 822, to validate local-part@domain email format
function IsValidEmail(P: PUTF8Char): boolean;

................................................................................
  public
    /// perform the space triming conversion to the specified value
    procedure Process(aFieldIndex: integer; var Value: RawUTF8); override;
  end;


{ ************ some other common types and conversion routines }

type
  /// calling context of TSynLogExceptionToStr callbacks
  TSynLogExceptionContext = record
    /// the raised exception class
    EClass: ExceptClass;
    /// the Delphi Exception instance
    // - may be nil for external/OS exceptions
    EInstance: Exception;
    /// the OS-level exception code
    // - could be $0EEDFAE0 of $0EEDFADE for Delphi-generated exceptions
    ECode: DWord;
    /// the address where the exception occured
    EAddr: PtrUInt;
    /// the current logging level
    // - may be either sllException or sllExceptionOS
    Level: TSynLogInfo;
  end;

  /// global hook callback to customize exceptions logged by TSynLog
  // - should return FALSE if Context.EAddr and Stack trace is to be appended
  TSynLogExceptionToStr = function(WR: TTextWriter; const Context: TSynLogExceptionContext): boolean;

  /// generic parent class of all custom Exception types of this unit
  ESynException = class(Exception)
  public
    /// constructor which will use FormatUTF8() instead of Format()
    // - expect % as delimitor, so is less error prone than %s %d %g
    // - will handle vtPointer/vtClass/vtObject/vtVariant kind of arguments,
    // appending class name for any class or object, the hexa value for a
    // pointer, or the JSON representation of the supplied variant
    constructor CreateUTF8(Format: PUTF8Char; const Args: array of const);
    {$ifndef NOEXCEPTIONINTERCEPT}
    /// can be used to customize how the exception is logged
    // - this default implementation will call the DefaultSynLogExceptionToStr()
    // function or the TSynLogExceptionToStrCustom global callback, if defined
    // - override this method to provide a custom logging content
    // - should return TRUE if Context.EAddr and Stack trace is not to be
    // written (i.e. as for any TSynLogExceptionToStr callback)
    function CustomLog(WR: TTextWriter; const Context: TSynLogExceptionContext): boolean; virtual;
    {$endif}
  end;

  /// exception raised by all TSynTable related code
  ETableDataException = class(ESynException);

  /// exception class associated to TDocVariant JSON/BSON document
  EDocVariant = class(ESynException);

var
  /// allow to customize the ESynException logging message
  TSynLogExceptionToStrCustom: TSynLogExceptionToStr = nil;

  {$ifndef NOEXCEPTIONINTERCEPT}
  /// default exception logging callback - will be set by the SynLog unit
  // - will add the default Exception details, including any Exception.Message
  // - if the exception inherits from ESynException
  // - returns TRUE: caller will then append ' at EAddr' and the stack trace
  DefaultSynLogExceptionToStr: TSynLogExceptionToStr = nil;
  {$endif}


/// convert a string into its INTEGER Curr64 (value*10000) representation
// - this type is compatible with Delphi currency memory map with PInt64(@Curr)^
// - fast conversion, using only integer operations
// - if NoDecimal is defined, will be set to TRUE if there is no decimal, AND
// the returned value will be an Int64 (not a PInt64(@Curr)^)
function StrToCurr64(P: PUTF8Char; NoDecimal: PBoolean=nil): Int64;
................................................................................
// to contain the overridden code buffer, for further hook disabling
procedure RedirectCode(Func, RedirectFunc: Pointer; Backup: PPatchCode=nil);

/// self-modifying code - restore a code from its RedirectCode() backup
procedure RedirectCodeRestore(Func: pointer; const Backup: TPatchCode);


























type
  /// to be used instead of TMemoryStream, for speed
  // - allocates memory from Delphi heap (i.e. FastMM4/SynScaleMM)
  // and not GlobalAlloc()
  // - uses bigger growing size of the capacity
{$ifdef LVCL} // LVCL already use Delphi heap instead of GlobalAlloc()
................................................................................
  // - JSON_OPTIONS[false] is e.g. _Json() and _JsonFmt() functions default
  // - JSON_OPTIONS[true] are used e.g. by _JsonFast() and _JsonFastFmt() functions
  JSON_OPTIONS: array[Boolean] of TDocVariantOptions = (
    [dvoReturnNullForUnknownProperty],
    [dvoReturnNullForUnknownProperty,dvoValueCopiedByReference]);

  /// TDocVariant options which may be used for plain JSON parsing
  // - this won't recognize any extended syntax
  JSON_OPTIONS_FAST_STRICTJSON: TDocVariantOptions =
    [dvoReturnNullForUnknownProperty,dvoValueCopiedByReference,
     dvoJSONParseDoNotTryCustomVariants];

{$endif NOVARIANTS}

{ ************ some console functions }

type
  /// available console colors (under Windows at least)
  TConsoleColor = (
    ccBlack, ccBlue, ccGreen, ccCyan, ccRed, ccMagenta, ccBrown, ccLightGray,
    ccDarkGray, ccLightBlue, ccLightGreen, ccLightCyan, ccLightRed, ccLightMagenta,
    ccYellow, ccWhite);

/// change the Windows console text writing color
// - call this procedure to initialize internal console process, if you manually
// intialized the Windows console, e.g. via the following code:
// ! AllocConsole;
// ! TextColor(ccLightGray);
procedure TextColor(Color: TConsoleColor);

/// change the Windows console text background color
procedure TextBackground(Color: TConsoleColor);

/// will wait for the ENTER key to be pressed, processing the internal
// Windows Message loop and any Synchronize() pending notification
// - to be used e.g. for proper work of console applications with interface-based
// service implemented as optExecInMainThread
procedure ConsoleWaitForEnterKey;

var
  /// low-level handle used for console writing
  // - may be overriden when console is redirected
  StdOut: THandle;


{ ******************* cross-cutting classes and functions ***** }

type
  /// pointer to ta high resolution timer object/record
  PPrecisionTimer = ^TPrecisionTimer;

  /// high resolution timer (for accurate speed statistics)
  // - WARNING: this record MUST be aligned to 32 bit, otherwise iFreq=0 -
................................................................................
    /// resume a paused timer
    procedure Resume;
    /// compute the per second count
    function PerSec(Count: cardinal): cardinal;
    /// compute the time elapsed by count, with appened time resolution (us,ms,s)
    function ByCount(Count: cardinal): RawUTF8;
  end;


{$ifdef MSWINDOWS}
{$ifndef DELPHI5OROLDER}

  /// a simple class which will set FPU exception flags for a code block
  // - using an IUnknown interface to let the compiler auto-generate a
  // try..finally block statement to reset the FPU exception register
  // - to be used e.g. as such:
  // !begin
  // !  TSynFPUException.ForLibrayCode;
  // !  ... now FPU exceptions will be ignored
................................................................................
    /// to be used to compute a Hash on the client, for a given Token
    // - the token should have been retrieved from the server, and the client
    // should compute and return this hash value, to perform the authentication
    // challenge and create the session
    class function ComputeHash(Token: Int64; const UserName,PassWord: RawUTF8): cardinal;
  end;



























































































































































































































































































/// convert a size to a human readable value
// - append MB, KB or B symbol
// - for MB and KB, add one fractional digit
function KB(bytes: Int64): RawUTF8;

/// convert a micro seconds elapsed time into a human readable value
................................................................................
function IntToThousandString(Value: integer; const ThousandSep: RawUTF8=','): RawUTF8;

/// return the Delphi Compiler Version
// - returns 'Delphi 2007' or 'Delphi 2010' e.g.
function GetDelphiCompilerVersion: RawUTF8;















































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































/// compress a data content using the SynLZ algorithm from one stream into another
// - returns the number of bytes written to Dest
// - you should specify a Magic number to be used to identify the block
function StreamSynLZ(Source: TCustomMemoryStream; Dest: TStream; Magic: cardinal): integer; overload;

/// uncompress using the SynLZ algorithm from one stream into another
// - returns a newly create memory stream containing the uncompressed data
................................................................................
// - you should specify a Magic number to be used to identify the block
// - this function will also recognize the block at the end of the source stream
// (if was appended to an existing data - e.g. a .mab at the end of a .exe)
// - on success, Source will point after all read data (so that you can e.g.
// append several data blocks to the same stream)
function StreamUnSynLZ(Source: TStream; Magic: cardinal): TMemoryStream; overload;

/// compute the real length of a given StreamSynLZ-compressed buffer
// - allows to replace an existing appended content, for instance
function StreamSynLZComputeLen(P: PAnsiChar; Len, aMagic: cardinal): integer;

/// uncompress using the SynLZ algorithm from one file into another
// - returns a newly create memory stream containing the uncompressed data
// - returns nil if source file is invalid (e.g. invalid name or invalid content)
// - you should specify a Magic number to be used to identify the block
// - this function will also recognize the block at the end of the source file
// (if was appended to an existing data - e.g. a .mab at the end of a .exe)
function StreamUnSynLZ(const Source: TFileName; Magic: cardinal): TMemoryStream; overload;
................................................................................

/// uncompress a memory bufer using the SynLZ algorithm and crc32c hashing
function SynLZDecompress(const Data: RawByteString): RawByteString; overload;

/// uncompress a memory bufer using the SynLZ algorithm and crc32c hashing
procedure SynLZDecompress(P: PAnsiChar; PLen: integer;out Result: RawByteString); overload;













resourcestring
  sInvalidIPAddress = '"%s" is an invalid IP v4 address';
  sInvalidEmailAddress = '"%s" is an invalid email address';
  sInvalidPattern = '"%s" does not match the expected pattern';
  sCharacter01n = 'character,character,characters';
  sInvalidTextLengthMin = 'Expect at least %d %s';
................................................................................

implementation

{$ifdef FPC}
uses
  SynFPCTypInfo // small wrapper unit around FPC's TypInfo.pp
  {$ifdef Linux}
  , SynFPCLinux,BaseUnix, Unix, dynlibs
  {$endif} ;
{$endif}


{ ************ some fast UTF-8 / Unicode / Ansi conversion routines }

var
................................................................................
    {$else}
    raise ESynException.CreateUTF8('%.AnsiBufferToUnicode() not supported yet for CP=%',
      [self,CodePage]);
    {$endif FPC}
    {$endif MSWINDOWS}
    {$endif ISDELPHIXE}
    {$ifndef DELPHI5OROLDER}
//    if result=Dest then
//      TSynLogTestLog.DebuggerNotify([GetLastError,CodePage],'Error % on CodePage %');
    {$endif}
  end;
  result^ := #0;
end;

function TSynAnsiConvert.AnsiBufferToUTF8(Dest: PUTF8Char;
  Source: PAnsiChar; SourceChars: Cardinal): PUTF8Char;
................................................................................
  if SupportSSE42 then
    crc32c := @crc32csse42 else
{$endif PUREPASCAL}
    crc32c := @crc32cfast;
  DefaultHasher := crc32c;
end;




{$ifdef MSWINDOWS}
const
  // lpMinimumApplicationAddress retrieved from Windows is very low $10000
  // - i.e. maximum number of ID per table would be 65536 in TSQLRecord.GetID
  // - so we'll force an higher and almost "safe" value as 1,048,576
  // (real value from runnning Windows is greater than $400000)
  MIN_PTR_VALUE = $100000;
................................................................................
  // see http://msdn.microsoft.com/en-us/library/ms724833(v=vs.85).aspx
  VER_NT_WORKSTATION = 1;
  VER_NT_DOMAIN_CONTROLLER = 2;
  VER_NT_SERVER = 3;
  SM_SERVERR2 = 89;
  PROCESSOR_ARCHITECTURE_AMD64 = 9;




{$ifndef UNICODE}
function GetVersionEx(var lpVersionInformation: TOSVersionInfoEx): BOOL; stdcall;
  external kernel32 name 'GetVersionExA';
{$endif}

function GetSystemTimeMillisecondsForXP: Int64; stdcall;
var fileTime: TFileTime;
................................................................................
   result := (result shl 32)+fileTime.dwLowDateTime;
   result := result div 10000;
   {$else}
   result := trunc(PInt64(@fileTime)^/10000); // 100 ns unit
   {$endif}
end;










procedure RetrieveSystemInfo;
var
  IsWow64Process: function(Handle: THandle; var Res: BOOL): BOOL; stdcall;
  GetNativeSystemInfo: procedure(var SystemInfo: TSystemInfo); stdcall;
  Res: BOOL;
  Kernel: THandle;
  P: pointer;
  Vers: TWindowsVersion;
begin
  Kernel := GetModuleHandle(kernel32);
  GetTickCount64 := GetProcAddress(Kernel,'GetTickCount64');
  if not Assigned(GetTickCount64) then
    GetTickCount64 := @GetSystemTimeMillisecondsForXP;

  IsWow64Process := GetProcAddress(Kernel,'IsWow64Process');
  Res := false;
  IsWow64 := Assigned(IsWow64Process) and
    IsWow64Process(GetCurrentProcess,Res) and Res;
  fillchar(SystemInfo,sizeof(SystemInfo),0);
  if IsWow64 then // see http://msdn.microsoft.com/en-us/library/ms724381(v=VS.85).aspx
    GetNativeSystemInfo := GetProcAddress(Kernel,'GetNativeSystemInfo') else
................................................................................
var i: Integer;
begin
  SetLength(Result,Source.Count);
  for i := 0 to Source.Count-1 do
    StringToUTF8(Source[i],Result[i]);
end;




/// find the position of the SEARCH] section in source
// - return true if SEARCH] was found, and store line after it in source
function FindSectionFirstLine(var source: PUTF8Char; search: PAnsiChar): boolean;
{$ifdef PUREPASCAL}
begin
  result := false;
  if source=nil then
................................................................................
      if VerQueryValue(Pt, '\', pointer(Info), Size2) then
        result := Info^.dwFileVersionMS;
    finally
      Freemem(Pt);
    end;
  end;
end;
{$endif DELPHI6OROLDER}

function WndProcMethod(Hwnd: HWND; Msg,wParam,lParam: integer): integer; stdcall;
var obj: TObject;
    dsp: TMessage;
begin
  {$ifdef CPU64}
  obj := pointer(GetWindowLongPtr(HWnd,GWLP_USERDATA));
  {$else}
  obj := pointer(GetWindowLong(HWnd,GWL_USERDATA)); // faster than GetProp()
  {$endif CPU64}
  if not Assigned(obj) then
    result := DefWindowProc(HWnd,Msg,wParam,lParam) else begin
    dsp.msg := Msg;
    dsp.wParam := WParam;
    dsp.lParam := lParam;
    dsp.result := 0;
    obj.Dispatch(dsp);
................................................................................
    exit; // impossible to create window -> fail
  {$ifdef CPU64}
  SetWindowLongPtr(result,GWLP_USERDATA,PtrInt(aObject));
  SetWindowLongPtr(result,GWLP_WNDPROC,PtrInt(@WndProcMethod));
  {$else}
  SetWindowLong(result,GWL_USERDATA,PtrInt(aObject)); // faster than SetProp()
  SetWindowLong(result,GWL_WNDPROC,PtrInt(@WndProcMethod));
  {$endif CPU64}
end;

function ReleaseInternalWindow(var aWindowName: string; var aWindow: HWND): boolean;
begin
  if (aWindow<>0) and (aWindowName<>'') then begin
    {$ifdef CPU64}
    SetWindowLongPtr(aWindow,GWLP_WNDPROC,PtrInt(@DefWindowProc));
    {$else}
    SetWindowLong(aWindow,GWL_WNDPROC,PtrInt(@DefWindowProc));
    {$endif CPU64}
    DestroyWindow(aWindow);
    Windows.UnregisterClass(pointer(aWindowName),hInstance);
    aWindow := 0;
    aWindowName := '';
    result := true;
  end else
    result := false;
end;

procedure ExeVersionRetrieve(DefaultVersion: integer);
const EXE_FMT: PUTF8Char = '% % (%)';
var Tmp: array[byte] of AnsiChar;
    TmpSize: cardinal;
    i: integer;
begin



  with ExeVersion do
  if Version=nil then begin
    ProgramFileName := paramstr(0);

    ProgramFilePath := ExtractFilePath(ProgramFileName);
    if IsLibrary then
      InstanceFileName := GetModuleName(HInstance) else
      InstanceFileName := ProgramFileName;
    Version := TFileVersion.Create(InstanceFileName,DefaultVersion);
    GarbageCollector.Add(Version);
    ProgramFullSpec := FormatUTF8(EXE_FMT,
      [ProgramFileName,Version.Detailed,DateTimeToIso8601(Version.BuildDateTime,True,' ')]);
    ProgramName := StringToUTF8(ExtractFileName(ProgramFileName));
    i := length(ProgramName);

    while i>0 do
      if ProgramName[i]='.' then begin
        SetLength(ProgramName,i-1);

        break;

      end else
      dec(i);
    TmpSize := sizeof(Tmp);
    GetComputerNameA(Tmp,TmpSize);
    Host := Tmp;
    TmpSize := sizeof(Tmp);
    GetUserNameA(Tmp,TmpSize);
    User := Tmp;














  end;
end;




















{ TFileVersion }

constructor TFileVersion.Create(const FileName: TFileName;
  DefaultVersion: integer);
var Size, Size2: DWord;
................................................................................
function TFileVersion.Version32: integer;
begin
  result := Major shl 16+Minor shl 8+Release;
end;

{$else}
















const
  _SC_PAGE_SIZE = $1000;

{$endif MSWINDOWS}



................................................................................
end;

{$endif}

function TDynArray.Add(const Elem): integer;
begin
  result := Count;
  if fValue=nil then
    exit; // avoid GPF if void
  SetCount(result+1);
  ElemCopy(Elem,pointer(PtrUInt(fValue^)+PtrUInt(result)*ElemSize)^);
end;

function TDynArray.New: integer;
begin
  result := Count;
  if fValue=nil then
    exit; // avoid GPF if void
  SetCount(result+1);
end;

procedure TDynArray.Insert(Index: Integer; const Elem);
var n: integer;
    P: PByteArray;
begin
  if fValue=nil then
    exit; // avoid GPF if void
  n := Count;
  SetCount(n+1);
  if cardinal(Index)<cardinal(n) then begin
    P := pointer(PtrUInt(fValue^)+PtrUInt(Index)*ElemSize);
    Move(P[0],P[ElemSize],cardinal(n-Index)*ElemSize);
    if ElemType<>nil then
      fillchar(P[0],ElemSize,0); // avoid GPF in ElemCopy() below
  end else
    // Index>=Count -> add at the end
    P := pointer(PtrUInt(fValue^)+PtrUInt(n)*ElemSize);
  ElemCopy(Elem,P^);
end;

procedure TDynArray.Clear;
begin
  SetCount(0);
end;

procedure TDynArray.Delete(aIndex: Integer);
var n, len: integer;
    P: PAnsiChar;
begin
  if fValue=nil then
    exit; // avoid GPF if void
  n := Count;
  if cardinal(aIndex)>=cardinal(n) then
    exit; // out of range
  dec(n);
  P := pointer(PtrUInt(fValue^)+PtrUInt(aIndex)*ElemSize);
  if ElemType<>nil then
    _Finalize(P,ElemType);
  if n>aIndex then begin
    len := cardinal(n-aIndex)*ElemSize;
    move(P[ElemSize],P[0],len);
    if ElemType<>nil then // avoid GPF
      fillchar(P[len],ElemSize,0);
................................................................................
  end;
  SetCount(n);
end;

function TDynArray.ElemPtr(aIndex: integer): pointer;
begin
  result := nil;
  if (fValue=nil) or (fValue^=nil) then
    exit;
  if fCountP<>nil then begin
    if cardinal(aIndex)>=PCardinal(fCountP)^ then
      exit;
  end else
    {$ifdef FPC}
    if cardinal(aIndex)>=cardinal(DynArrayLength(Value^)) then
    {$else}
    if cardinal(aIndex)>=PCardinal(PtrUInt(fValue^)-sizeof(PtrInt))^ then
    {$endif}
      exit;
  result := pointer(PtrUInt(fValue^)+PtrUInt(aIndex)*ElemSize);
end;

function TDynArray.GetCount: integer;
begin
  if fValue<>nil then
    if fCountP=nil then
      if PtrInt(fValue^)<>0 then
        {$ifdef FPC}
        result := DynArrayLength(Value^) else
        {$else}
        result := PInteger(PtrUInt(fValue^)-sizeof(PtrInt))^ else
        {$endif}
        result := 0 else
      result := fCountP^ else
    result := 0; // avoid GPF if void
end;

procedure Exchg(P1,P2: PAnsiChar; max: integer);
................................................................................
    P1, P2: PAnsiChar;
    c: AnsiChar;
    i64: Int64;
begin
  n := Count-1;
  if n>0 then begin
    siz := ElemSize;
    P1 := fValue^;
    case siz of
    1: begin
      // optimized version for TByteDynArray and such
      P2 := P1+n;
      for i := 1 to n shr 1 do begin
        c := P1^;
        P1^ := P2^;
................................................................................
end;

procedure TDynArray.SaveToStream(Stream: TStream);
var Posi, PosiEnd: Integer;
    MemStream: TCustomMemoryStream absolute Stream;
    tmp: RawByteString;
begin
  if (fValue=nil) or (Stream=nil) then
    exit; // avoid GPF if void
  if Stream.InheritsFrom(TCustomMemoryStream) then begin
    Posi := MemStream.Seek(0,soFromCurrent);
    PosiEnd := Posi+SaveToLength;
    if PosiEnd>MemStream.Size then
      MemStream.Size := PosiEnd;
    if SaveTo(PAnsiChar(MemStream.Memory)+Posi)-MemStream.Memory<>PosiEnd then
................................................................................
end;

function TDynArray.SaveTo(Dest: PAnsiChar): PAnsiChar;
var i, n, LenBytes: integer;
    P: PAnsiChar;
    FieldTable: PFieldTable;
begin
  if fValue=nil then begin
    result := Dest;
    exit; // avoid GPF if void
  end;
  // first store the element size+type to check for the format (name='' mostly)
  Dest := pointer(ToVarUInt32(ElemSize,pointer(Dest)));
  if ElemType=nil then
    Dest^ := #0 else
................................................................................
  if n=0 then begin
    result := Dest;
    exit;
  end;
  inc(Dest,sizeof(Cardinal)); // leave space for Hash32 checksum
  result := Dest;
  // store dynamic array elements content
  P := fValue^;
  if ElemType=nil then begin
    // binary types: store as once
    n := n*integer(ElemSize);
    move(P^,Dest^,n);
    inc(Dest,n);
  end else
    case PTypeKind(ElemType)^ of
................................................................................
  result := Dest;
end;

function TDynArray.SaveToLength: integer;
var i,n,L: integer;
    P: PAnsiChar;
begin
  if fValue=nil then begin
    result := 0;
    exit; // avoid GPF if void
  end;
  n := Count;
  result := ToVarUInt32Length(ElemSize)+ToVarUInt32Length(n)+1;
  if n=0 then
    exit;
  if ElemType=nil then
    inc(result,integer(ElemSize)*n) else begin
    P := fValue^;
    case PTypeKind(ElemType)^ of
    tkLString, tkWString:
      for i := 1 to n do begin
        if PPtrUInt(P)^=0 then
          inc(result) else
          inc(result,ToVarUInt32LengthWithData(PInteger(PPtrUInt(P)^-sizeof(integer))^));
        inc(P,sizeof(PtrUInt));
................................................................................
end;

const
  PTRSIZ = sizeof(Pointer);
  KNOWNTYPE_SIZE: array[TDynArrayKind] of byte = (
    0, 1, 2, 4,4,4, 8,8,8,8,8, PTRSIZ,PTRSIZ,PTRSIZ,PTRSIZ,PTRSIZ,
    {$ifndef NOVARIANTS}sizeof(Variant),{$endif} 0);

function TDynArray.GetArrayTypeName: RawUTF8;
begin
  TypeInfoToName(fTypeInfo,result);
end;

function TDynArray.ToKnownType: TDynArrayKind;
var FieldTable: PFieldTable;
label Bin, Rec;
begin
  if fKnownType<>djNone then begin
    result := fKnownType;
    exit;
................................................................................
    T: TDynArrayKind;
    wasString, expectedString, isValid: boolean;
    EndOfObject: AnsiChar;
    Val: PUTF8Char;
    CustomReader: TDynArrayJSONCustomReader;
begin // code below must match TTextWriter.AddDynArrayJSON()
  result := nil;
  if (P=nil) or (fValue=nil) then
    exit;
  P := GotoNextNotSpace(P);
  if P^<>'[' then
    exit;
  repeat inc(P) until P^<>' ';
  n := JSONArrayCount(P);
  if n<0 then
................................................................................
      exit; // invalid content
  end else begin
    Count := n; // fast allocation of the whole dynamic array memory at once
    case T of
    {$ifndef NOVARIANTS}
    djVariant:
      for i := 0 to n-1 do 
        P := VariantLoadJSON(PVariantArray(fValue^)^[i],P,@EndOfObject,@JSON_OPTIONS[true]);
    {$endif}
    djCustom: begin
      Val := fValue^;
      for i := 1 to n do begin
        P := CustomReader(P,Val^,isValid);
        if not isValid then
          exit;
        EndOfObject := P^; // ',' or ']' for the last item of the array
        inc(P);
        inc(Val,ElemSize);
................................................................................
    else begin
      expectedString := (T in [djTimeLog..djSynUnicode]);
      for i := 0 to n-1 do begin
        Val := GetJSONField(P,P,@wasString,@EndOfObject);
        if (Val=nil) or (wasString<>expectedString) then
          exit;
        case T of
        djByte:     PByteArray(fValue^)^[i] := GetCardinal(Val);
        djWord:     PWordArray(fValue^)^[i] := GetCardinal(Val);
        djInteger:  PIntegerArray(fValue^)^[i] := GetInteger(Val);
        djCardinal: PCardinalArray(fValue^)^[i] := GetCardinal(Val);
        djSingle:   PSingleArray(fValue^)^[i] := GetExtended(Val);
        djInt64:    SetInt64(Val,PInt64Array(fValue^)^[i]);
        djTimeLog:  PInt64Array(fValue^)^[i] := Iso8601ToTimeLogPUTF8Char(Val,0);
        djDateTime: Iso8601ToDateTimePUTF8CharVar(Val,0,TDateTime(PDoubleArray(fValue^)^[i]));
        djDouble:   PDoubleArray(fValue^)^[i] := GetExtended(Val);
        djCurrency: PInt64Array(fValue^)^[i] := StrToCurr64(Val);
        djRawUTF8:  RawUTF8(PPointerArray(fValue^)^[i]) := Val;
        djWinAnsi:  WinAnsiConvert.UTF8BufferToAnsi(Val,StrLen(Val),RawByteString(PPointerArray(fValue^)^[i]));
        djString:   UTF8DecodeToString(Val,StrLen(Val),string(PPointerArray(fValue^)^[i]));
        djWideString: UTF8ToWideString(Val,StrLen(Val),WideString(PPointerArray(fValue^)^[i]));
        djSynUnicode: UTF8ToSynUnicode(Val,StrLen(Val),SynUnicode(PPointerArray(fValue^)^[i]));
        end;
      end;
    end;
    end;
  end;
  if aEndOfObject<>nil then
    aEndOfObject^ := EndOfObject;
................................................................................
  // check stored element size+type
  if Source=nil then begin
    Clear;
    result := nil;
    exit;
  end;
  FromVarUInt32(PByte(Source)); // ignore StoredElemSize to be Win32/64 compatible
  if (fValue=nil) or
     //((ElemSize<>sizeof(pointer)) and (StoredElemSize<>ElemSize)) or
     ((ElemType=nil) and (Source^<>#0) or
     ((ElemType<>nil) and (Source^=#0{<>PAnsiChar(ElemType)^}))) then begin
     // ignore ElemType^ to be cross-FPC/Delphi compatible
    result := nil; // invalid Source content
    exit;
  end;
................................................................................
    result := Source;
    exit;
  end;
  // retrieve security checksum
  Hash := pointer(Source);
  inc(Source,sizeof(cardinal));
  // retrieve dynamic array elements content
  P := fValue^;
  if ElemType=nil then begin
    // binary type was stored as once
    n := n*integer(ElemSize);
    move(Source^,P^,n);
    inc(Source,n);
  end else
  case PTypeKind(ElemType)^ of
................................................................................
      aCompare: TDynArraySortCompare): integer;
var n, L, cmp: integer;
    P: PAnsiChar;
begin
  n := Count;
  if (@aCompare<>nil) and (n>0) then begin
    dec(n);
    P := fValue^;
    if (n>10) and (length(aIndex)>n) then begin
      // array should be sorted -> use fast binary search
      L := 0;
      repeat
        result := (L+n) shr 1;
        cmp := aCompare(P[cardinal(aIndex[result])*ElemSize],Elem);
        if cmp=0 then
................................................................................
end;

function TDynArray.FindAndFill(var Elem; aIndex: PIntegerDynArray=nil;
  aCompare: TDynArraySortCompare=nil): integer;
begin
  result := FindIndex(Elem,aIndex,aCompare);
  if result>=0 then // if found, fill Elem with the matching item
    ElemCopy(PAnsiChar(fValue^)[cardinal(result)*ElemSize],Elem);
end;

function TDynArray.FindAndDelete(var Elem; aIndex: PIntegerDynArray=nil;
  aCompare: TDynArraySortCompare=nil): integer;
begin
  result := FindIndex(Elem,aIndex,aCompare);
  if result>=0 then
................................................................................
end;

function TDynArray.FindAndUpdate(const Elem; aIndex: PIntegerDynArray=nil;
  aCompare: TDynArraySortCompare=nil): integer;
begin
  result := FindIndex(Elem,aIndex,aCompare);
  if result>=0 then // if found, fill Elem with the matching item
    ElemCopy(Elem,PAnsiChar(fValue^)[cardinal(result)*ElemSize]);
end;

function TDynArray.FindAndAddIfNotExisting(const Elem; aIndex: PIntegerDynArray=nil;
  aCompare: TDynArraySortCompare=nil): integer;
begin
  result := FindIndex(Elem,aIndex,aCompare);
  if result<0 then
................................................................................
function TDynArray.Find(const Elem): integer;
var n, L, cmp: integer;
    P: PAnsiChar;
begin
  n := Count;
  if (@fCompare<>nil) and (n>0) then begin
    dec(n);
    P := fValue^;
    if fSorted and (n>10) then begin
      // array is sorted -> use fast binary search
      L := 0;
      repeat
        result := (L+n) shr 1;
        cmp := fCompare(P[cardinal(result)*ElemSize],Elem);
        if cmp=0 then
................................................................................
    L := I;
  until I >= R;
end;

procedure TDynArray.Sort;
var QuickSort: TDynArrayQuickSort;
begin
  if (@fCompare<>nil) and (fValue<>nil) and (fValue^<>nil) then begin
    Quicksort.Compare := @fCompare;
    Quicksort.Value := fValue^;
    Quicksort.ElemSize := ElemSize;
    Quicksort.QuickSort(0,Count-1);
    fSorted := true;
  end;
end;

procedure TDynArray.CreateOrderedIndex(var aIndex: TIntegerDynArray;
  aCompare: TDynArraySortCompare);
var QuickSort: TDynArrayQuickSort;
    n: integer;
begin
  if (@aCompare<>nil) and (fValue<>nil) and (fValue^<>nil) then begin
    n := Count;
    if length(aIndex)<n then begin
      SetLength(aIndex,n);
      FillIncreasing(pointer(aIndex),0,n);
    end;
    Quicksort.Compare := @aCompare;
    Quicksort.Value := fValue^;
    Quicksort.ElemSize := ElemSize;
    Quicksort.Index := pointer(aIndex);
    Quicksort.QuickSortIndexed(0,n-1);
  end;
end;

function TDynArray.ElemEquals(const A,B): boolean;
................................................................................
begin
  result := false;
  if ArrayType<>B.ArrayType then
    exit; // array types shall match
  n := Count;
  if n<>B.Count then
    exit;
  P1 := fValue^;
  P2 := B.fValue^;
  if @fCompare<>nil then // if a customized comparison is available, use it
    for i := 1 to n do
      if fCompare(P1^,P2^)<>0 then
        exit else begin
        inc(P1,ElemSize);
        inc(P2,ElemSize);
      end else
................................................................................
end;
{$endif}

function TDynArray.IndexOf(const Elem): integer;
var P: pointer;
    max: integer;
begin
  if fValue=nil then begin
    result := -1;
    exit; // avoid GPF if void
  end;
  max := Count-1;
  P := fValue^;
  if @Elem<>nil then
  if ElemType=nil then
  case ElemSize of
    // optimized versions for arrays of byte,word,integer,Int64,Currency,Double
    1: for result := 0 to max do
         if PByteArray(P)^[result]=byte(Elem) then exit;
    2: for result := 0 to max do
................................................................................
  result := -1;
end;

procedure TDynArray.Init(aTypeInfo: pointer; var aValue; aCountPointer: PInteger=nil);
var Typ: PDynArrayTypeInfo absolute aTypeInfo;
begin
  fTypeInfo := aTypeInfo;
  fValue := @aValue;
  if Typ^.Kind<>tkDynArray then
    raise ESynException.CreateUTF8('Not a dynamic array: %',[PShortString(@Typ^.NameLen)^]);
  {$ifdef FPC_REQUIRES_PROPER_ALIGNMENT}
  Typ := GetFPCAlignPtr(Typ);
  {$else}
  inc(PtrUInt(Typ),Typ^.NameLen);
  {$endif}
................................................................................
  fCompare := Comp;
  fKnownType := aKind;
  fKnownSize := KNOWNTYPE_SIZE[aKind];
end;

procedure TDynArray.Void;
begin
  fValue := nil;
end;

function TDynArray.IsVoid: boolean;
begin
  result := fValue=nil;
end;

procedure _DynArrayClear(var a: Pointer; typeInfo: Pointer);
{$ifdef FPC}
  [external name 'FPC_DYNARRAY_CLEAR'];
{$else}
asm
................................................................................
    OldLength, NeededSize, minLength: PtrUInt;
    pp: pointer;
begin // this method is faster than default System.DynArraySetLength() function
  // check that new array length is not just a hidden finalize
  if NewLength=0 then begin
    {$ifndef NOVARIANTS}
    if ArrayType=TypeInfo(TVariantDynArray) then
      VariantDynArrayClear(TVariantDynArray(fValue^)) else
    {$endif}
      _DynArrayClear(fValue^,ArrayType);
    exit;
  end;
  // retrieve old length
  p := fValue^;
  if p<>nil then begin
    dec(PtrUInt(p),Sizeof(TDynArrayRec)); // p^ = start of heap object
    OldLength := p^.length;
  end else
    OldLength := 0;
  // calculate the needed size of the resulting memory structure on heap
  NeededSize := NewLength*ElemSize+Sizeof(TDynArrayRec);
................................................................................
    GetMem(p,neededSize);
    minLength := oldLength;
    if minLength>newLength then
      minLength := newLength;
    if ElemType<>nil then begin
      pp := pa+Sizeof(TDynArrayRec);
      FillChar(pp^,minLength*elemSize,0);
      CopyArray(pp,fValue^,ElemType,minLength)
    end else
      Move(fValue^,pa[Sizeof(TDynArrayRec)],minLength*elemSize);
  end;
  // set refCnt=1 and new length to the heap memory structure
  with p^ do begin
    refCnt := 1;
    {$ifdef FPC}
    high := newLength-1;
    {$else}
................................................................................
    length := newLength;
    {$endif}
  end;
  Inc(PtrUInt(p),Sizeof(p^));
  // reset new allocated elements content to zero 
  OldLength := OldLength*elemSize;
  FillChar(pa[OldLength],neededSize-OldLength-Sizeof(TDynArrayRec),0);
  fValue^ := p;
end;

procedure TDynArray.SetCount(aCount: integer);
const MINIMUM_SIZE = 64;
var capa, delta: integer;
begin
  fSorted := false;
  if fValue=nil then
    exit; // avoid GPF if void
  if fCountP<>nil then begin
    delta := aCount-fCountP^;
    if delta=0 then
      exit;
    fCountP^ := aCount;
    if PtrInt(fValue^)=0 then begin
      // no capa yet
      if (delta>0) and (aCount<MINIMUM_SIZE) then
        aCount := MINIMUM_SIZE; // reserve some minimal space for Add()
    end else begin
      capa := DynArrayLength(fValue^);
      if delta>0 then begin
        // size-up -> grow by chunks
        if capa>=fCountP^ then
          exit; // no need to grow
        inc(capa,capa shr 2);
        if capa<fCountP^ then
          aCount := fCountP^ else
................................................................................
  end;
  // no external Count, array size-down or array up-grow -> realloc
  InternalSetLength(aCount);
end;

function TDynArray.GetCapacity: integer;
begin // capacity := length(DynArray)
  if (fValue<>nil) and (PtrInt(fValue^)<>0) then
    result := DynArrayLength(fValue^) else
    result := 0;
end;

procedure TDynArray.SetCapacity(aCapacity: integer);
begin
  if fValue=nil then
    exit; // avoid GPF if void
  if fCountP<>nil then
    if fCountP^>aCapacity then
      fCountP^ := aCapacity;
  InternalSetLength(aCapacity);
end;

................................................................................
    fSorted := false;
  end;
end;

procedure TDynArray.Copy(Source: TDynArray);
var n: Cardinal;
begin
  if (fValue=nil) or (Source.fValue=nil) or (ArrayType<>Source.ArrayType) then
    Exit;
  SetCapacity(Source.Capacity);
  n := Source.Count;
  if ElemType=nil then
    move(Source.fValue^^,fValue^^,n*ElemSize) else
    CopyArray(fValue^,Source.fValue^,ElemType,n);
end;

procedure TDynArray.CopyFrom(const Source; MaxElem: integer);
var SourceDynArray: TDynArray;
begin
  SourceDynArray.Init(fTypeInfo,pointer(@Source)^);
  SourceDynArray.fCountP := @MaxElem; // would set Count=0 at Init()
................................................................................
end;

procedure TDynArray.Slice(var Dest; aCount: Cardinal; aFirstIndex: cardinal=0);
var n: Cardinal;
    D: PPointer;
    P: PAnsiChar;
begin
  if fValue=nil then
    exit; // avoid GPF if void
  n := Count;
  if aFirstIndex>=n then
    aCount := 0 else
  if aCount>=n-aFirstIndex then
    aCount := n-aFirstIndex;
  DynArray(ArrayType,Dest).InternalSetLength(aCount);
  D := @Dest;
  if aCount>0 then begin
    P := PAnsiChar(fValue^)+aFirstIndex*ElemSize;
    if ElemType=nil then
      move(P^,D^^,aCount*ElemSize) else
      CopyArray(D^,P,ArrayType,aCount);
  end;
end;

procedure TDynArray.AddArray(const DynArray; aStartIndex: integer=0; aCount: integer=-1);
var DynArrayCount, n: integer;
    D: PPointer;
    PS,PD: pointer;
begin
  if fValue=nil then
    exit; // avoid GPF if void
  D := @DynArray;
  if D^=nil then  // inline GetCount
    DynArrayCount := 0 else
    DynArrayCount := DynArrayLength(D^);
  if aStartIndex>=DynArrayCount then
    exit; // nothing to copy
................................................................................
  if (aCount<0) or (cardinal(aStartIndex+aCount)>cardinal(DynArrayCount)) then
    aCount := DynArrayCount-aStartIndex;
  if aCount<=0 then
    exit;
  n := Count;
  SetCount(n+aCount);
  PS := pointer(PtrUInt(D^)+cardinal(aStartIndex)*ElemSize);
  PD := pointer(PtrUInt(fValue^)+cardinal(n)*ElemSize);
  if ElemType=nil then
    move(PS^,PD^,cardinal(aCount)*ElemSize) else
    CopyArray(PD,PS,ArrayType,aCount);
end;

procedure TDynArray.ElemClear(var Elem);
begin
................................................................................
end;

procedure TDynArrayHashed.SetCapacity(aCapacity: Integer);
begin
  InternalDynArray.SetCapacity(aCapacity);
end;

function TDynArrayHashed.fValue: PPointer;
begin
  result := InternalDynArray.fValue;
end;

function TDynArrayHashed.ElemSize: PtrUInt;
begin
  result := InternalDynArray.ElemSize;
end;

................................................................................
    repeat
      aName := aName_+UInt32ToUTF8(j);
      ndx := FindHashedForAdding(aName,added);
      inc(j);
    until added;
  end;
  assert(ndx=Count-1);
  result := PAnsiChar(fValue^)+cardinal(ndx)*ElemSize;
  PRawUTF8(result)^ := aName; // store unique name at 1st elem position
end;

function TDynArrayHashed.AddUniqueName(const aName: RawUTF8;
  ExceptionMsg: PUTF8Char; const ExceptionArgs: array of const): pointer;
var ndx: integer;
    added: boolean;
begin
  ndx := FindHashedForAdding(aName,added);
  if added then begin
    assert(ndx=Count-1);
    result := PAnsiChar(fValue^)+cardinal(ndx)*ElemSize;
    PRawUTF8(result)^ := aName; // store unique name at 1st elem position
  end else
    if ExceptionMsg=nil then
      raise ESynException.CreateUTF8('Duplicated "%" name',[aName]) else
      raise ESynException.CreateUTF8(ExceptionMsg,ExceptionArgs);
end;

................................................................................
function TDynArrayHashed.FindHashedAndFill(var Elem): integer;
var P: PAnsiChar;
begin
  result := HashFind(HashOneFromTypeInfo(Elem),Elem);
  if result<0 then
    result := -1 else begin
    // copy from dynamic array found entry into Elem = Fill
    P := PAnsiChar(fValue^)+cardinal(result)*ElemSize;
    if ElemType=nil then
      move(P^,Elem,ElemSize) else
      CopyArray(@Elem,P,ElemType,1);
  end;
end;

function TDynArrayHashed.FindHashedAndUpdate(var Elem; AddIfNotExisting: boolean): integer;
................................................................................
  if aHashCode=HASH_VOID then
    aHashCode := HASH_ONVOIDCOLISION; // as in HashFind() -> for HashAdd() below
  result := HashFind(aHashCode,Elem);
  if result<0 then
    if AddIfNotExisting then begin
      // not existing -> add as new element
      HashAdd(Elem,aHashCode,result); // ReHash only if necessary
      P := PAnsiChar(fValue^)+cardinal(result)*ElemSize;
      if ElemType=nil then
        move(Elem,P^,ElemSize) else
        CopyArray(P,@Elem,ElemType,1);
    end else
      result := -1 else begin
    // copy from Elem into dynamic array found entry = Update
    P := PAnsiChar(fValue^)+cardinal(result)*ElemSize;
    if ElemType=nil then
      move(Elem,P^,ElemSize) else
      CopyArray(P,@Elem,ElemType,1);
    ReHash;
  end;
end;

................................................................................
  result := (aHashCode-1) and (n-1); // fHashs[] has a power of 2 length
  first := result;
  repeat
    with fHashs[result] do
    if Hash=aHashCode then
      if not Assigned(fEventCompare) then
        if @{$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$endif}fCompare<>nil then begin
          if {$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$endif}fCompare(PAnsiChar(fValue^)[Index*ElemSize],Elem)=0 then begin
            result := Index;
            exit; // found -> returns index in dynamic array
          end;
        end else begin
        if {$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$endif}ElemEquals(PAnsiChar(fValue^)[Index*ElemSize],Elem) then begin
          result := Index;
          exit; // found -> returns index in dynamic array
        end;
      end else begin
        if fEventCompare(PAnsiChar(fValue^)[Index*ElemSize],Elem)=0 then begin
          result := Index;
          exit; // found -> returns index in dynamic array
        end;
      end else
    if Hash=HASH_VOID then begin
      result := -(result+1);
      exit; // not found -> returns void index in fHashs[] as negative
................................................................................

function TDynArrayHashed.GetHashFromIndex(aIndex: Integer): Cardinal;
var P: pointer;
begin
  if cardinal(aIndex)>=cardinal(Count) then
    result := 0 else begin
    // it's faster to rehash than to loop in fHashs[].Index values
    P := PAnsiChar(fValue^)+cardinal(aIndex)*ElemSize;
    result := HashOneFromTypeInfo(P^);
    if result=HASH_VOID then
      result := HASH_ONVOIDCOLISION; // 0 means void slot in the loop below
  end;
end;

function TDynArrayHashed.HashOneFromTypeInfo(const Elem): cardinal;
................................................................................
var i, n, ndx: integer;
    P: PAnsiChar;
    aHashCode: cardinal;
begin
  HashInit;
  n := Count;
  if n>0 then begin // avoid GPF after TDynArray.Clear call (Count=0)
    P := fValue^;
    for i := 0 to n-1 do begin
      if @aHasher=nil then
        aHashCode := HashOneFromTypeInfo(P^) else
        aHashCode := aHasher(P^);
      if aHashCode=HASH_VOID then
        aHashCode := HASH_ONVOIDCOLISION; // 0 means void slot in the loop below
      ndx := HashFind(aHashCode,P^);
................................................................................
    Add('[',']');
    exit;
  end;
  if GlobalJSONCustomParsers.DynArraySearch(
      aDynArray.ArrayType,aDynArray.ElemType,customWriter,@customParser) then
    T := djCustom else
    T := aDynArray.ToKnownType;
  P := aDynArray.fValue^;
  Add('[');
  case T of
  djNone: begin
    tmp := aDynArray.SaveTo;
    WrBase64(pointer(tmp),length(tmp),true); // magic=true
  end;
  djCustom: begin
................................................................................
  dec(B);
end;

function TTextWriter.LastChar: AnsiChar;
begin
  result := B^;
end;

function TTextWriter.PendingBytes: PtrUInt;
begin
  result := B-fTempBuf;
end;

procedure TTextWriter.CancelLastComma;
begin
  if B^=',' then
    dec(B);
end;

................................................................................
  P := @PByteArray(fTempBuf)[fEchoStart];
  while (L>0) and (P[L-1] in [10,13]) do // trim right CR/LF chars
    dec(L);
  LI := length(fEchoBuf); // faster append to fEchoBuf
  SetLength(fEchoBuf,LI+L);
  Move(P^,PByteArray(fEchoBuf)[LI],L);
end;

procedure TTextWriter.EchoReset;
begin
  fEchoBuf := '';
end;


{ TJSONWriter }

procedure TJSONWriter.CancelAllVoid;
const VOIDARRAY: PAnsiChar = '[]'#10;
      VOIDFIELD: PAnsiChar = '{"FieldCount":0}';
................................................................................
  MinLowerCount := 1;
  MinUpperCount := 1;
  MaxSpaceCount := 0;
  // read custom parameters
  inherited;
end;


{ ************ some console functions }

var
  TextAttr: integer = ord(ccDarkGray);

{$ifdef MSWINDOWS}

procedure InitConsole;
begin
  if StdOut=0 then begin
   StdOut := GetStdHandle(STD_OUTPUT_HANDLE);
   if StdOut=INVALID_HANDLE_VALUE then
     StdOut := 0;
  end;
end;

procedure TextColor(Color: TConsoleColor);
var oldAttr: integer;
begin
  InitConsole;
  oldAttr := TextAttr;
  TextAttr := (TextAttr and $F0) or ord(Color);
  if TextAttr<>oldAttr then
    SetConsoleTextAttribute(StdOut,TextAttr);
end;

procedure TextBackground(Color: TConsoleColor);
var oldAttr: integer;
begin
  InitConsole;
  oldAttr := TextAttr;
  TextAttr := (TextAttr and $0F) or (ord(Color) shl 4);
  if TextAttr<>oldAttr then
    SetConsoleTextAttribute(StdOut,TextAttr);
end;

procedure ConsoleWaitForEnterKey;
{$ifdef DELPHI5OROLDER}
begin
  readln;
end;
{$else}
  function KeyPressed(ExpectedKey: Word):Boolean;
  var lpNumberOfEvents: DWORD;
      lpBuffer: TInputRecord;
      lpNumberOfEventsRead : DWORD;
      nStdHandle: THandle;
  begin
    result := false;
    nStdHandle := GetStdHandle(STD_INPUT_HANDLE);
    lpNumberOfEvents := 0;
    GetNumberOfConsoleInputEvents(nStdHandle,lpNumberOfEvents);
    if lpNumberOfEvents<>0 then begin
      PeekConsoleInput(nStdHandle,lpBuffer,1,lpNumberOfEventsRead);
      if lpNumberOfEventsRead<>0 then
        if lpBuffer.EventType=KEY_EVENT then
          if lpBuffer.Event.KeyEvent.bKeyDown and
             ((ExpectedKey=0) or (lpBuffer.Event.KeyEvent.wVirtualKeyCode=ExpectedKey)) then
            result := true else
            FlushConsoleInputBuffer(nStdHandle) else
          FlushConsoleInputBuffer(nStdHandle);
    end;
  end;
var msg: TMsg;
begin
  while not KeyPressed(VK_RETURN) do begin
    {$ifndef LVCL}
    if GetCurrentThreadID=MainThreadID then
      CheckSynchronize{$ifdef WITHUXTHEME}(1000){$endif}  else
    {$endif}
      WaitMessage;
    while PeekMessage(msg,0,0,0,PM_REMOVE) do
      if Msg.Message=WM_QUIT then
        exit else begin
        TranslateMessage(Msg);
        DispatchMessage(Msg);
      end;
  end;
end;
{$endif DELPHI5OROLDER}

{$else MSWINDOWS}

// we by-pass crt.pp since this unit cancels the SIGINT signal

procedure TextColor(Color: TConsoleColor);
const AnsiTbl : string[8]='04261537';
begin
  if ord(color)>=8 then
    write(#27'[1;3') else
    write(#27'[0;3');
  write(AnsiTbl[(ord(color) and 7)+1],'m');
end;

procedure TextBackground(Color: TConsoleColor);
begin // not implemented yet
end;

procedure ConsoleWaitForEnterKey;
begin
  Readln;
end;

{$endif MSWINDOWS}



{ ************ Unit-Testing classes and functions }

function KB(bytes: Int64): RawUTF8;
begin
  if bytes>=1024*1024 then begin
    if bytes>=1024*1024*1024 then begin
................................................................................
  {$endif CONDITIONALEXPRESSIONS}
{$endif}
{$ifdef CPU64}
  +' 64 bit'
{$endif}
end;







































































































































































































































































































































































































































































































































































































{ TSynCache }

procedure TSynCache.Add(const aValue: RawUTF8; aTag: PtrInt);
begin
  if (self=nil) or (fFindLastAddedIndex<0) then
    // fFindLastAddedIndex should have been set by a previous call to Find() 
    exit;
................................................................................
      if ValuesCount=0 then
        break;
    end;
    fStream.Write(pointer(fBuf)^,fPos);
    fPos := 0;
  until false;
end;

function TFileBufferWriter.WriteDirectStart(maxSize: integer;
  const TooBigMessage: RawUTF8): PByte;
begin
  if fPos+maxSize>fBufLen then
    fTotalWritten := Flush;
  if fPos+maxSize>fBufLen then 
    raise ESynException.CreateUTF8(
      '%.WriteDirectStart: too big %',[self,TooBigMessage]);
  result := @PByteArray(fBuf)^[fPos];
end;

procedure TFileBufferWriter.WriteDirectEnd(realSize: integer);
begin
  if fPos+realSize>fBufLen then
    raise ESynException.CreateUTF8(
      '%.WriteDirectEnd: too big %',[self,realSize]);
  inc(fPos,realSize);
  inc(fTotalWritten,realSize);
end;


{ TFileBufferReader }

procedure TFileBufferReader.Close;
begin
  fMap.UnMap;
................................................................................
  CheckVTableInitialized;
  Result := VTable.Validate(Pointer(VValue),RecordIndex);
end;

{$endif DELPHI5OROLDER}







type
  TSynLZHead = packed record
    Magic: cardinal;
    CompressedSize: integer;
    HashCompressed: cardinal;
    UnCompressedSize: integer;
    HashUncompressed: cardinal;
................................................................................
  end;
  PSynLZHead = ^TSynLZHead;
  TSynLZTrailer = packed record
    HeaderRelativeOffset: cardinal;
    Magic: cardinal;
  end;
  PSynLZTrailer = ^TSynLZTrailer;

function StreamSynLZComputeLen(P: PAnsiChar; Len, aMagic: cardinal): integer;
begin
  inc(P,Len);
  with PSynLZTrailer(P-sizeof(TSynLZTrailer))^ do
    if (Magic=aMagic) and (HeaderRelativeOffset<Len) and
       (PSynLZHead(P-HeaderRelativeOffset)^.Magic=aMagic) then
      // trim existing content
      result := Len-HeaderRelativeOffset else
      result := Len;
end;

function StreamSynLZ(Source: TCustomMemoryStream; Dest: TStream; Magic: cardinal): integer;
var DataLen: integer;
    S: pointer;
    P: pointer;
    Head: TSynLZHead;
    Trailer: TSynLZTrailer;
................................................................................
       {$endif}
       (crc32c(0,pointer(result),len)<>PCardinal(P)^)) then
      result := '';
  end;
  end;
end;







































































































































































































































































































































































































































































































































































































































{ ESynException }

constructor ESynException.CreateUTF8(Format: PUTF8Char; const Args: array of const);
begin
  Create(UTF8ToString(FormatUTF8(Format,Args)));
end;

{$ifndef NOEXCEPTIONINTERCEPT}

function ESynException.CustomLog(WR: TTextWriter;
  const Context: TSynLogExceptionContext): boolean;
begin
  if Assigned(TSynLogExceptionToStrCustom) then
    result := TSynLogExceptionToStrCustom(WR,Context) else
  if Assigned(DefaultSynLogExceptionToStr) then





























































































































































































































































    result := DefaultSynLogExceptionToStr(WR,Context) else



















































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































    result := false;
end;



{$endif}





































































































































{ TMemoryMapText }

constructor TMemoryMapText.Create;
begin
end;
................................................................................
function TMemoryMapText.GetString(aIndex: integer): string;
begin
  if (self=nil) or (cardinal(aIndex)>=cardinal(fCount)) then
    result := '' else
    UTF8DecodeToString(fLines[aIndex],GetLineSize(fLines[aIndex],fMapEnd),result);
end;

function GetLineContains(p,pEnd, up: PUTF8Char): boolean;
var i: integer;
label Fnd;
begin
  if (p<>nil) and (up<>nil) then
  if pEnd=nil then
    repeat
      i := ord(p^);
................................................................................
procedure TMemoryMapText.AddInMemoryLinesClear;
begin
  dec(fCount,fAppendedLinesCount);
  fAppendedLinesCount := 0;
  fAppendedLines := nil;
end;



























































































































































































































































































































































































































































































































































































{ TRawByteStringStream }

constructor TRawByteStringStream.Create(const aString: RawByteString);
begin
  fDataString := aString;
end;
................................................................................
  if i<0 then
    result := aDefaultValue else
    result := List[i].Value;
end;

function TSynNameValue.Initialized: boolean;
begin
  result := fDynArray.fValue=@List;
end;

function TSynNameValue.GetBlobData: RawByteString;
begin
  result := fDynArray.SaveTo;
end;

................................................................................
end;

procedure GarbageCollectorFreeAndNil(var InstanceVariable; Instance: TObject);
begin
  TObject(InstanceVariable) := Instance;
  GarbageCollectorFreeAndNilList.Add(@InstanceVariable);
end;

var
  GlobalCriticalSection: TRTLCriticalSection;
  GlobalCriticalSectionInitialized: boolean;

procedure GlobalLock;
begin
  if not GlobalCriticalSectionInitialized then begin
    InitializeCriticalSection(GlobalCriticalSection);
    GlobalCriticalSectionInitialized := true;
  end;
  EnterCriticalSection(GlobalCriticalSection);
end;

procedure GlobalUnLock;
begin
  if GlobalCriticalSectionInitialized then
    LeaveCriticalSection(GlobalCriticalSection);
end;


initialization
  // initialization of global variables
  GarbageCollectorFreeAndNilList := TList.Create;
  GarbageCollectorFreeAndNil(GarbageCollector,TObjectList.Create);
  {$ifndef FPC}
  {$ifndef CPUARM}

Changes to SynDB.pas.

290
291
292
293
294
295
296
297

298
299
300
301
302
303
304
  Classes,
  {$ifndef LVCL}
  Contnrs,
  {$endif}
  {$ifndef DELPHI5OROLDER}
  Variants,
  {$endif}
  SynCommons;


  
{ -------------- TSQLDB* generic classes and types }

type
  /// generic Exception type, as used by the SynDB unit
  ESQLDBException = class(ESynException);







|
>







290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
  Classes,
  {$ifndef LVCL}
  Contnrs,
  {$endif}
  {$ifndef DELPHI5OROLDER}
  Variants,
  {$endif}
  SynCommons,
  SynLog;

  
{ -------------- TSQLDB* generic classes and types }

type
  /// generic Exception type, as used by the SynDB unit
  ESQLDBException = class(ESynException);

Changes to SynDBDataset.pas.

58
59
60
61
62
63
64

65
66
67
68
69
70
71
uses
  Windows, SysUtils,
  {$IFNDEF DELPHI5OROLDER}
  Variants,
  {$ENDIF}
  Classes, Contnrs,
  SynCommons,

  SynDB,
  {$ifdef ISDELPHIXE2}
  Data.DB, Data.FMTBcd;
  {$else}
  DB, FMTBcd;{$endif}









>







58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
uses
  Windows, SysUtils,
  {$IFNDEF DELPHI5OROLDER}
  Variants,
  {$ENDIF}
  Classes, Contnrs,
  SynCommons,
  SynLog,
  SynDB,
  {$ifdef ISDELPHIXE2}
  Data.DB, Data.FMTBcd;
  {$else}
  DB, FMTBcd;{$endif}


Changes to SynDBDataset/SynDBBDE.pas.

62
63
64
65
66
67
68

69
70
71
72
73
74
75
uses
  Windows, SysUtils,
  {$IFNDEF DELPHI5OROLDER}
  Variants,
  {$ENDIF}
  Classes, Contnrs,
  SynCommons,

  SynDB,
  DBTables,
  SynDBDataset;


{ -------------- BDE database engine connection }








>







62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
uses
  Windows, SysUtils,
  {$IFNDEF DELPHI5OROLDER}
  Variants,
  {$ENDIF}
  Classes, Contnrs,
  SynCommons,
  SynLog,
  SynDB,
  DBTables,
  SynDBDataset;


{ -------------- BDE database engine connection }

Changes to SynDBDataset/SynDBFireDAC.pas.

61
62
63
64
65
66
67

68
69
70
71
72
73
74
uses
  Windows, SysUtils,
  {$IFNDEF DELPHI5OROLDER}
  Variants,
  {$ENDIF}
  Classes, Contnrs,
  SynCommons,

  SynDB,
  SynDBDataset,
  {$ifdef ISDELPHIXE5}
  FireDAC.Comp.Client, FireDAC.Stan.Param;
  {$else}
  uADCompClient, uADStanParam;
  {$endif}







>







61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
uses
  Windows, SysUtils,
  {$IFNDEF DELPHI5OROLDER}
  Variants,
  {$ENDIF}
  Classes, Contnrs,
  SynCommons,
  SynLog,
  SynDB,
  SynDBDataset,
  {$ifdef ISDELPHIXE5}
  FireDAC.Comp.Client, FireDAC.Stan.Param;
  {$else}
  uADCompClient, uADStanParam;
  {$endif}

Changes to SynDBDataset/SynDBNexusDB.pas.

64
65
66
67
68
69
70

71
72
73
74
75
76
77
  Windows, SysUtils,
  {$IFNDEF DELPHI5OROLDER}
  Variants,
  {$ENDIF}
  Classes, Contnrs,
  {$ifdef ISDELPHIXE2}Data.DB,{$else}DB,{$endif}
  SynCommons,

  SynDB,
  SynDBDataset,
  nxDB,
  nxsdServerEngine,
  nxsrServerEngine,
  nxsqlEngine,
  nxlgEventLog,







>







64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
  Windows, SysUtils,
  {$IFNDEF DELPHI5OROLDER}
  Variants,
  {$ENDIF}
  Classes, Contnrs,
  {$ifdef ISDELPHIXE2}Data.DB,{$else}DB,{$endif}
  SynCommons,
  SynLog,
  SynDB,
  SynDBDataset,
  nxDB,
  nxsdServerEngine,
  nxsrServerEngine,
  nxsqlEngine,
  nxlgEventLog,

Changes to SynDBDataset/SynDBUniDAC.pas.

60
61
62
63
64
65
66

67
68
69
70
71
72
73
uses
  Windows, SysUtils,
  {$IFNDEF DELPHI5OROLDER}
  Variants,
  {$ENDIF}
  Classes, Contnrs,
  SynCommons,

  SynDB,
  SynDBDataset,
  Uni,
  UniProvider,
  UniScript;









>







60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
uses
  Windows, SysUtils,
  {$IFNDEF DELPHI5OROLDER}
  Variants,
  {$ENDIF}
  Classes, Contnrs,
  SynCommons,
  SynLog,
  SynDB,
  SynDBDataset,
  Uni,
  UniProvider,
  UniScript;


Changes to SynDBODBC.pas.

87
88
89
90
91
92
93

94
95
96
97
98
99
100
  SysUtils,
  {$ifndef DELPHI5OROLDER}
  Variants,
  {$endif}
  Classes,
  Contnrs,
  SynCommons,

  SynDB;


{ -------------- TODBC* classes and types implementing an ODBC library connection  }

type
  /// generic Exception type, generated for ODBC connection







>







87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
  SysUtils,
  {$ifndef DELPHI5OROLDER}
  Variants,
  {$endif}
  Classes,
  Contnrs,
  SynCommons,
  SynLog,
  SynDB;


{ -------------- TODBC* classes and types implementing an ODBC library connection  }

type
  /// generic Exception type, generated for ODBC connection

Changes to SynDBOracle.pas.

147
148
149
150
151
152
153

154
155
156
157
158
159
160
  SysUtils,
  {$ifndef DELPHI5OROLDER}
  Variants,
  {$endif}
  Classes,
  Contnrs,
  SynCommons,

  SynDB;

{ -------------- Oracle Client Interface native connection  }

type
  /// execption type associated to the native Oracle Client Interface (OCI)
  ESQLDBOracle = class(ESynException);







>







147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
  SysUtils,
  {$ifndef DELPHI5OROLDER}
  Variants,
  {$endif}
  Classes,
  Contnrs,
  SynCommons,
  SynLog,
  SynDB;

{ -------------- Oracle Client Interface native connection  }

type
  /// execption type associated to the native Oracle Client Interface (OCI)
  ESQLDBOracle = class(ESynException);

Changes to SynDBSQLite3.pas.

103
104
105
106
107
108
109

110
111
112
113
114
115
116
  Variants,
  {$endif}
  Classes,
  {$ifndef LVCL}
  Contnrs,
  {$endif}
  SynCommons,

  SynSQLite3,
  SynDB;

{ -------------- SQlite3 database engine native connection  }

type
  /// will implement properties shared by the SQLite3 engine







>







103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
  Variants,
  {$endif}
  Classes,
  {$ifndef LVCL}
  Contnrs,
  {$endif}
  SynCommons,
  SynLog,
  SynSQLite3,
  SynDB;

{ -------------- SQlite3 database engine native connection  }

type
  /// will implement properties shared by the SQLite3 engine

Changes to SynDBZEOS.pas.

120
121
122
123
124
125
126

127
128
129
130
131
132
133
uses
  Windows, Types, SysUtils,
  {$IFNDEF DELPHI5OROLDER}
  Variants,
  {$ENDIF}
  Classes, Contnrs,
  SynCommons,

  SynDB,
  // load physical providers as defined by ENABLE_* in Zeos.inc
  // -> you can patch your local Zeos.inc and comment these defines to
  // exclude database engines you don't need
  {$IFNDEF UNIX}
  {$IFDEF ENABLE_ADO}
  ZDbcAdo,







>







120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
uses
  Windows, Types, SysUtils,
  {$IFNDEF DELPHI5OROLDER}
  Variants,
  {$ENDIF}
  Classes, Contnrs,
  SynCommons,
  SynLog,
  SynDB,
  // load physical providers as defined by ENABLE_* in Zeos.inc
  // -> you can patch your local Zeos.inc and comment these defines to
  // exclude database engines you don't need
  {$IFNDEF UNIX}
  {$IFDEF ENABLE_ADO}
  ZDbcAdo,

Added SynLog.pas.











































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
/// logging functions used by Synopse projects
// - this unit is a part of the freeware Synopse mORMot framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SynLog;

(*
    This file is part of Synopse framework.

    Synopse framework. Copyright (C) 2014 Arnaud Bouchez
      Synopse Informatique - http://synopse.info

  *** BEGIN LICENSE BLOCK *****
  Version: MPL 1.1/GPL 2.0/LGPL 2.1

  The contents of this file are subject to the Mozilla Public License Version
  1.1 (the "License"); you may not use this file except in compliance with
  the License. You may obtain a copy of the License at
  http://www.mozilla.org/MPL

  Software distributed under the License is distributed on an "AS IS" basis,
  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  for the specific language governing rights and limitations under the License.

  The Original Code is Synopse framework.

  The Initial Developer of the Original Code is Arnaud Bouchez.

  Portions created by the Initial Developer are Copyright (C) 2014
  the Initial Developer. All Rights Reserved.

  Contributor(s):

  Alternatively, the contents of this file may be used under the terms of
  either the GNU General Public License Version 2 or later (the "GPL"), or
  the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  in which case the provisions of the GPL or the LGPL are applicable instead
  of those above. If you wish to allow use of your version of this file only
  under the terms of either the GPL or the LGPL, and not to allow others to
  use your version of this file under the terms of the MPL, indicate your
  decision by deleting the provisions above and replace them with the notice
  and other provisions required by the GPL or the LGPL. If you do not delete
  the provisions above, a recipient may use your version of this file under
  the terms of any one of the MPL, the GPL or the LGPL.

  ***** END LICENSE BLOCK *****

  Version 1.18
  - first public release, extracted from SynCommons.pas unit
  - Delphi XE4/XE5/XE6/XE7 compatibility (Windows target platform only)
  - unit fixed and tested with Delphi XE2 (and up) 64-bit compiler under Windows
  - added TSynLogFile.Freq read-only property
  - added DefaultSynLogExceptionToStr() function and TSynLogExceptionToStrCustom
    variable, and ESynException.CustomLog() method to customize how raised
    exception are logged when intercepted - feature request [495720e0b9]
  - added ESynException.CreateUTF8() constructor, more powerful than the
    default Exception.CreateFmt(): this CreateUTF8 method is now used everywhere
  - added TSynLogFamily.EndOfLineCRLF properties
  - added TSynLogFamily's NoFile and EchoCustom properties - see [91a114d2f6]
  - TSynLog will now append only the execution time when leaving a method,
    without the class/method name (smaller log file, and less resource use)
  - TSynLog header now contains system environment variables
  - added TSynLog.DebuggerNotify() and TSynLog.CloseLogFile / Release methods
  - protected the global TSynLog instances list against potential race condition
  - introducing TSynLogFamily.StackTraceUse: TSynLogStackTraceUse property
    (set to stManualAndAPI by default, but stOnlyAPI within the Delphi IDE)
  - introducing TSynLogFamily.EchoToConsole: TSynLogInfos property, able to
    optionally echo the process log to the current console window, using colors
  - added TSynLogFamily.EchoRemoteStart() and EchoRemoteStop methods
  - added TSynLog.Void class function
  - if new property TSynLogFamily.PerThreadLog is set to ptIdentifiedInOnFile,
    a new column will be added for each logged row - LogViewer has been updated
    to allow easy and efficient multi-thread process logging
  - introducing TSynLogFamily.RotateFileCount and associated RotateFileSizeKB,
    RotateFileDailyAtHour and OnRotate properties, to enable log file rotation
    by size or at given hour - request [72feb66d45] + [b3e8cc8424]
  - added TSynLog.CustomFileName property - see [d8fbc10bf8]
  - added TSynLog.ComputeFileName virtual method and TSynLogFamily.FileExistsAction
    property for feature request [d029051dcb]
  - added TSynLog/ISynLog.LogLines() method for direct multi-line text logging
  - added optional TextTruncateAtLength parameter for TSynLog/ISynLog.Log()
  - declared TSynLog.LogInternal() methods as virtual - request [e47c64fb2c]
  - .NET/CLR external exceptions will now be logged with their C# type name
  - special 'SetThreadName' exception will now be ignored by TSynLog hook
  - fixed ticket [19e567b8ca] about TSynLog issue in heavily concurrent mode:
    now a per-thread context will be stored, e.g. for Enter/Leave tracking
  - fixed ticket [a516b1a954] about ptOneFilePerThread log file rotation

*)


{$I Synopse.inc} // define HASINLINE USETYPEINFO CPU32 CPU64 OWNNORMTOUPPER

interface

uses
{$ifdef MSWINDOWS}
  Windows,
  Messages,
{$endif}
{$ifdef KYLIX3}
  Types,
{$endif}
  Classes,
{$ifndef LVCL}
  SyncObjs, // for TEvent
  Contnrs,  // for TObjectList
{$ifdef HASINLINE}
  Types,
{$endif}
{$endif}
{$ifndef NOVARIANTS}
  Variants,
{$endif}
  SysUtils,
  SynLZ, // needed e.g. for TSynMapFile .mab format
  SynCommons;


{ ************ Logging classes and functions }

type
  /// a debugger symbol, as decoded by TSynMapFile from a .map file
  TSynMapSymbol = record
    /// symbol internal name
    Name: RawUTF8;
    /// starting offset of this symbol in the executable
    Start: cardinal;
    /// end offset of this symbol in the executable
    Stop: cardinal;
  end;
  PSynMapSymbol = ^TSynMapSymbol;
  /// a dynamic array of symbols, as decoded by TSynMapFile from a .map file
  TSynMapSymbolDynArray = array of TSynMapSymbol;

  /// a debugger unit, as decoded by TSynMapFile from a .map file
  TSynMapUnit = record
    /// Name, Start and Stop of this Unit
    Symbol: TSynMapSymbol;
    /// associated source file name
    FileName: RawUTF8;
    /// list of all mapped source code lines of this unit
    Line: TIntegerDynArray;
    /// start code address of each source code lin
    Addr: TIntegerDynArray;
  end;
  /// a dynamic array of units, as decoded by TSynMapFile from a .map file
  TSynMapUnitDynArray = array of TSynMapUnit;

  {$M+}
  /// retrieve a .map file content, to be used e.g. with TSynLog to provide
  // additional debugging information
  // - original .map content can be saved as .mab file in a more optimized format
  TSynMapFile = class
  protected
    fMapFile: TFileName;
    fSymbol: TSynMapSymbolDynArray;
    fUnit: TSynMapUnitDynArray;
    fSymbols: TDynArray;
    fUnits: TDynArrayHashed;
    fGetModuleHandle: PtrUInt;
    fHasDebugInfo: boolean;
  public
    /// get the available debugging information
    // - if aExeName is specified, will use it in its search for .map/.mab
    // - if aExeName is not specified, will use the currently running .exe/.dll
    // - it will first search for a .map matching the file name: if found,
    // will be read to retrieve all necessary debugging information - a .mab
    // file will be also created in the same directory (if MabCreate is TRUE)
    // - if .map is not not available, will search for the .mab file
    // - if no .mab is available, will search for a .mab appended to the .exe/.dll
    // - if nothing is available, will log as hexadecimal pointers, without
    // debugging information
    constructor Create(const aExeName: TFileName=''; MabCreate: boolean=true);
    /// save all debugging information in the .mab custom binary format
    // - if no file name is specified, it will be saved as ExeName.mab or DllName.mab
    // - this file content can be appended to the executable via SaveToExe method
    // - this function returns the created file name
    function SaveToFile(const aFileName: TFileName=''): TFileName;
    /// save all debugging informat in our custom binary format
    procedure SaveToStream(aStream: TStream);
    /// append all debugging information to an executable (or library)
    // - the executable name must be specified, because it's impossible to
    // write to the executable of a running process
    // - this method will work for .exe and for .dll (or .ocx)
    procedure SaveToExe(const aExeName: TFileName);
    /// add some debugging information according to the specified memory address
    // - will create a global TSynMapFile instance for the current process, if
    // necessary
    // - if no debugging information is available (.map or .mab), will write
    // the address as hexadecimal
    class procedure Log(W: TTextWriter; Addr: PtrUInt);
    /// retrieve a symbol according to an absolute code address
    function FindSymbol(aAddr: cardinal): integer;
    /// retrieve an unit and source line, according to an absolute code address
    function FindUnit(aAddr: cardinal; out LineNumber: integer): integer;
    /// return the symbol location according to the supplied absolute address
    // - i.e. unit name, symbol name and line number (if any), as plain text
    // - returns '' if no match found
    function FindLocation(aAddr: Cardinal): RawUTF8;
    /// all symbols associated to the executable
    property Symbols: TSynMapSymbolDynArray read fSymbol;
    /// all units, including line numbers, associated to the executable
    property Units: TSynMapUnitDynArray read fUnit;
  published
    /// the associated file name
    property FileName: TFileName read fMapFile;
    /// equals true if a .map or .mab debugging information has been loaded
    property HasDebugInfo: boolean read fHasDebugInfo;
  end;
  {$M-}

  {$M+} { we need the RTTI for the published methods of the logging classes }

  TSynLog = class;
  TSynLogClass = class of TSynLog;
  TSynLogFamily = class;
  TSynLogFile = class;
  
  {$M-}

  /// a generic interface used for logging a method
  // - you should create one TSynLog instance at the beginning of a block code
  // using TSynLog.Enter: the ISynLog will be released automaticaly by the
  // compiler at the end of the method block, marking it's executation end
  // - all logging expect UTF-8 encoded text, i.e. usualy English text
  ISynLog = interface(IUnknown)
    ['{527AC81F-BC41-4717-B089-3F74DE56F1AE}']
{$ifndef DELPHI5OROLDER}
    /// call this method to add some information to the log at a specified level
    // - see the format in TSynLog.Log() method description
    // (not compatible with default SysUtils.Format function)
    // - if Instance is set, it will log the corresponding class name and address
    // (to be used if you didn't call TSynLog.Enter() method first)
    // - note that cardinal values should be type-casted to Int64() (otherwise
    // the integer mapped value will be transmitted, therefore wrongly)
    procedure Log(Level: TSynLogInfo; TextFmt: PWinAnsiChar; const TextArgs: array of const;
      Instance: TObject=nil); overload;
{$endif}
    /// call this method to add some information to the log at a specified level
    // - if Instance is set and Text is not '', it will log the corresponding
    // class name and address (to be used e.g. if you didn't call TSynLog.Enter()
    // method first)
    // - if Instance is set and Text is '', will behave the same as
    // Log(Level,Instance), i.e. write the Instance as JSON content
    procedure Log(Level: TSynLogInfo; const Text: RawUTF8;
      Instance: TObject=nil; TextTruncateAtLength: integer=maxInt); overload;
    /// call this method to add the content of an object to the log at a
    // specified level
    // - TSynLog will write the class and hexa address - TSQLLog will write the
    // object JSON content
    procedure Log(Level: TSynLogInfo; Instance: TObject); overload;
    /// call this method to add the content of most low-level types to the log
    // at a specified level
    // - TSynLog will handle enumerations and dynamic array; TSQLLog will be
    // able to write TObject/TSQLRecord and sets content as JSON
    procedure Log(Level: TSynLogInfo; aName: PWinAnsiChar;
      aTypeInfo: pointer; var aValue; Instance: TObject=nil); overload;
    /// call this method to add the caller address to the log at the specified level
    // - if the debugging info is available from TSynMapFile, will log the
    // unit name, associated symbol and source code line
    procedure Log(Level: TSynLogInfo=sllTrace); overload;
    /// call this method to add some multi-line information to the log at a
    // specified level
    // - LinesToLog content will be added, one line per one line, delimited by
    // #13#10 (CRLF)
    // - if a line starts with IgnoreWhenStartWith (already uppercase), it won't
    // be added to the log content (to be used e.g. with '--' for SQL statements)
    procedure LogLines(Level: TSynLogInfo; LinesToLog: PUTF8Char;
      aInstance: TObject=nil; const IgnoreWhenStartWith: PAnsiChar=nil);
    /// retrieve the associated logging instance
    function Instance: TSynLog;
  end;

  /// this event can be set for a TSynLogFamily to archive any deprecated log
  // into a custom compressed format
  // - will be called by TSynLogFamily when TSynLogFamily.Destroy identify
  // some outdated files
  // - the aOldLogFileName will contain the .log file with full path
  // - the aDestinationPath parameter will contain 'ArchivePath\log\YYYYMM\'
  // - should return true on success, false on error
  // - example of matching event handler are EventArchiveDelete/EventArchiveSynLZ
  // or EventArchiveZip in SynZip.pas
  // - this event handler will be called one time per .log file to archive,
  // then one last time with aOldLogFileName='' in order to close any pending
  // archive (used e.g. by EventArchiveZip to open the .zip only once)
  TSynLogArchiveEvent = function(const aOldLogFileName, aDestinationPath: TFileName): boolean;

  /// this event can be set for a TSynLogFamily to customize the file rotation
  // - will be called by TSynLog.PerformRotation
  // - should return TRUE if the function did process the file name
  // - should return FALSE if the function did not do anything, so that the
  // caller should perform the rotation as usual  
  TSynLogRotateEvent = function(aLog: TSynLog; const aOldLogFileName: TFileName): boolean;

  /// how threading is handled by the TSynLogFamily
  // - by default, ptMergedInOneFile will indicate that all threads are logged
  // in the same file, in occurence order
  // - if set to ptOneFilePerThread, it will create one .log file per thread
  // - if set to ptIdentifiedInOnFile, a new column will be added for each
  // log row, with the corresponding ThreadID - LogView tool will be able to
  // display per-thread logging, if needed - note that your application shall
  // use a thread pool (just like all mORMot servers classes do), otherwise
  // some random hash collision may occur if Thread IDs are not recycled enough
  TSynLogPerThreadMode = (
    ptMergedInOneFile, ptOneFilePerThread, ptIdentifiedInOnFile);

  /// how stack trace shall be computed during logging
  TSynLogStackTraceUse = (stManualAndAPI,stOnlyAPI,stOnlyManual);

  /// how file existing shall be handled during logging
  TSynLogExistsAction = (acOverwrite, acAppend, acAppendWithHeader);

  {/ regroup several logs under an unique family name
   - you should usualy use one family per application or per architectural
     module: e.g. a server application may want to log in separate files the
     low-level Communication, the DB access, and the high-level process
   - initialize the family settings before using them, like in this code:
     ! with TSynLogDB.Family do begin
     !   Level := LOG_VERBOSE;
     !   PerThreadLog := ptOneFilePerThread;
     !   DestinationPath := 'C:\Logs';
     ! end;
   - then use the logging system inside a method:
     ! procedure TMyDB.MyMethod;
     ! var ILog: ISynLog;
     ! begin
     !   ILog := TSynLogDB.Enter(self,'MyMethod');
     !   // do some stuff
     !   ILog.Log(sllInfo,'method called');
     ! end; }
  TSynLogFamily = class
  protected
    fLevel, fLevelStackTrace: TSynLogInfos;
    fArchiveAfterDays: Integer;
    fArchivePath: TFileName;
    fOnArchive: TSynLogArchiveEvent;
    fOnRotate: TSynLogRotateEvent;
    fPerThreadLog: TSynLogPerThreadMode;
    fIncludeComputerNameInFileName: boolean;
    fCustomFileName: TFileName;
    fGlobalLog: TSynLog;
    fSynLogClass: TSynLogClass;
    fIdent: integer;
    fDestinationPath: TFileName;
    fDefaultExtension: TFileName;
    fBufferSize: integer;
    fHRTimeStamp: boolean;
    fWithUnitName: boolean;
    fNoFile: boolean;
    {$ifdef MSWINDOWS}
    fAutoFlush: cardinal;
    {$endif}
    {$ifndef NOEXCEPTIONINTERCEPT}
    fHandleExceptions: boolean;
    {$endif}
    fStackTraceLevel: byte;
    fStackTraceUse: TSynLogStackTraceUse;
    fFileExistsAction: TSynLogExistsAction;
    fExceptionIgnore: TList;
    fEchoToConsole: TSynLogInfos;
    fEchoCustom: TOnTextWriterEcho;
    fEchoRemoteClient: TObject;
    fEchoRemoteClientOwned: boolean;
    fEchoRemoteEvent: TOnTextWriterEcho;
    fEndOfLineCRLF: boolean;
    fDestroying: boolean;
    fRotateFileCurrent: cardinal;
    fRotateFileCount: cardinal;
    fRotateFileSize: cardinal;
    fRotateFileAtHour: integer;
    function CreateSynLog: TSynLog;
    {$ifdef MSWINDOWS}
    procedure SetAutoFlush(TimeOut: cardinal);
    {$endif}
    procedure SetDestinationPath(const value: TFileName);
    procedure SetLevel(aLevel: TSynLogInfos);
    procedure SetEchoToConsole(aEnabled: TSynLogInfos);
  public
    /// intialize for a TSynLog class family
    // - add it in the global SynLogFileFamily[] list
    constructor Create(aSynLog: TSynLogClass);
    /// release associated memory
    // - will archive older DestinationPath\*.log files, according to
    // ArchiveAfterDays value and ArchivePath
    destructor Destroy; override;

    /// retrieve the corresponding log file of this thread and family
    // - creates the TSynLog if not already existing for this current thread
    function SynLog: TSynLog;
    /// register one object and one echo callback for remote logging
    // - aClient is typically a mORMot's TSQLHttpClient
    // - if aClientOwnedByFamily is TRUE, its life time will be manage by this
    // TSynLogFamily: it will staty alive until this TSynLogFamily is destroyed,
    // or the EchoRemoteStop() method called
    // - aClientEvent should be able to send the log row to the remote server
    // - typical use may be:
    procedure EchoRemoteStart(aClient: TObject; const aClientEvent: TOnTextWriterEcho;
      aClientOwnedByFamily: boolean);
    /// stop echo remote logging
    // - will free the aClient instance supplied to EchoRemoteStart
    procedure EchoRemoteStop;

    /// you can add some exceptions to be ignored to this list
    // - for instance, EConvertError may be added to the list
    property ExceptionIgnore: TList read fExceptionIgnore;
    /// event called to archive the .log content after a defined delay
    // - Destroy will parse DestinationPath folder for *.log files matching
    // ArchiveAfterDays property value
    // - you can set this property to EventArchiveDelete in order to delete deprecated
    // files, or EventArchiveSynLZ to compress the .log file into our propertary
    // SynLZ format: resulting file name will be ArchivePath\log\YYYYMM\*.log.synlz
    // (use FileUnSynLZ function to uncompress it)
    // - if you use SynZip.EventArchiveZip, the log files will be archived in
    // ArchivePath\log\YYYYMM.zip
    // - the aDestinationPath parameter will contain 'ArchivePath\log\YYYYMM\'
    // - this event handler will be called one time per .log file to archive,
    // then one last time with aOldLogFileName='' in order to close any pending
    // archive (used e.g. by EventArchiveZip to open the .zip only once)
    property OnArchive: TSynLogArchiveEvent read fOnArchive write fOnArchive;
    /// event called to perform a custom file rotation
    // - will be checked by TSynLog.PerformRotation to customize the rotation
    // process and do not perform the default step, if the callback returns TRUE
    property OnRotate: TSynLogRotateEvent read fOnRotate write fOnRotate;
    /// if the some kind of events shall be echoed to the console
    // - note that it will slow down the logging process a lot (console output
    // is slow by nature under Windows, but may be convenient for interactive
    // debugging of services, for instance
    // - this property shall be set before any actual logging, otherwise it
    // will have no effect
    // - can be set e.g. to LOG_VERBOSE in order to echo every kind of events
    // - EchoCustom or EchoService can be activated separately
    property EchoToConsole: TSynLogInfos read fEchoToConsole write SetEchoToConsole;
    /// can be set to a callback which will be called for each log line
    // - could be used with a third-party logging system
    // - EchoToConsole or EchoService can be activated separately
    // - you may even disable the integrated file output, via NoFile := true
    property EchoCustom: TOnTextWriterEcho read fEchoCustom write fEchoCustom;
  published
    /// the associated TSynLog class
    property SynLogClass: TSynLogClass read fSynLogClass;
    /// index in global SynLogFileFamily[] and SynLogFileIndexThreadVar[] lists
    property Ident: integer read fIdent;
    /// the current level of logging information for this family
    // - can be set e.g. to LOG_VERBOSE in order to log every kind of events
    property Level: TSynLogInfos read fLevel write SetLevel;
    /// the levels which will include a stack trace of the caller
    // - by default, contains sllError, sllException, sllExceptionOS, sllFail,
    // sllLastError and sllStackTrace
    // - exceptions will always trace the stack
    property LevelStackTrace: TSynLogInfos read fLevelStackTrace write fLevelStackTrace;
    /// the folder where the log must be stored
    // - by default, is in the executable folder
    property DestinationPath: TFileName read fDestinationPath write SetDestinationPath;
    /// the file extension to be used
    // - is '.log' by default
    property DefaultExtension: TFileName read fDefaultExtension write fDefaultExtension;
    /// if TRUE, the log file name will contain the Computer name - as '(MyComputer)'
    property IncludeComputerNameInFileName: boolean read fIncludeComputerNameInFileName write fIncludeComputerNameInFileName;
    /// can be used to customized the default file name
    // - by default, the log file name is computed from the executable name
    // (and the computer name if IncludeComputerNameInFileName is true)
    // - you can specify your own file name here, to be used instead
    // - this file name should not contain any folder, nor file extension (which
    // are set by DestinationPath and DefaultExtension properties) 
    property CustomFileName: TFileName read fCustomFileName write fCustomFileName;
    /// the folder where old log files must be compressed
    // - by default, is in the executable folder, i.e. the same as DestinationPath
    // - the 'log\' sub folder name will always be appended to this value
    // - will then be used by OnArchive event handler to produce, with the
    // current file date year and month, the final path (e.g.
    // 'ArchivePath\Log\YYYYMM\*.log.synlz' or 'ArchivePath\Log\YYYYMM.zip')
    property ArchivePath: TFileName read fArchivePath write fArchivePath;
    /// number of days before OnArchive event will be called to compress
    // or delete deprecated files
    // - will be set by default to 7 days
    // - will be used by Destroy to call OnArchive event handler on time
    property ArchiveAfterDays: Integer read fArchiveAfterDays write fArchiveAfterDays;
    /// the internal in-memory buffer size, in bytes
    // - this is the number of bytes kept in memory before flushing to the hard
    // drive; you can call TSynLog.Flush method or set AutoFlushTimeOut to true
    // in order to force the writting to disk
    // - is set to 4096 by default (4 KB is the standard hard drive cluster size)
    property BufferSize: integer read fBufferSize write fBufferSize;
    /// define how thread will be identified during logging process
    // - by default, ptMergedInOneFile will indicate that all threads are logged
    // in the same file, in occurence order (so multi-thread process on server
    // side may be difficult to interpret)
    // - if RotateFileCount and RotateFileSizeKB/RotateFileDailyAtHour are set,
    // will be ignored (internal thread list shall be defined for one process)
    property PerThreadLog: TSynLogPerThreadMode read fPerThreadLog write fPerThreadLog;
    /// if TRUE, will log high-resolution time stamp instead of ISO 8601 date and time
    // - this is less human readable, but allows performance profiling of your
    // application on the customer side (using TSynLog.Enter methods)
    // - set to FALSE by default, or if RotateFileCount and RotateFileSizeKB /
    // RotateFileDailyAtHour are set (the high resolution frequency is set
    // in the log file header, so expects a single file) 
    property HighResolutionTimeStamp: boolean read fHRTimeStamp write fHRTimeStamp;
    /// if TRUE, will log the unit name with an object instance if available
    // - unit name is available from RTTI if the class has published properties
    // - set to FALSE by default
    property WithUnitName: boolean read fWithUnitName write fWithUnitName;
    {$ifdef MSWINDOWS}
    /// the time (in seconds) after which the log content must be written on
    // disk, whatever the current content size is
    // - by default, the log file will be written for every 4 KB of log - this
    // will ensure that the main application won't be slow down by logging
    // - in order not to loose any log, a background thread can be created
    // and will be responsible of flushing all pending log content every
    // period of time (e.g. every 10 seconds)
    property AutoFlushTimeOut: cardinal read fAutoFlush write SetAutoFlush;
    {$endif}
    /// force no log to be written to any file
    // - may be usefull in conjunction e.g. with EchoToConsole or any other
    // third-party logging component 
    property NoFile: boolean read fNoFile write fNoFile;
    /// auto-rotation of logging files
    // - set to 0 by default, meaning no rotation
    // - can be set to a number of rotating files: rotation and compression will 
    // happen, and main file size will be up to RotateFileSizeKB number of bytes,
    // or when RotateFileDailyAtHour time is reached
    // - if set to 1, no .synlz backup will be created, so the main log file will
    // be restarted from scratch when it reaches RotateFileSizeKB size or when
    // RotateFileDailyAtHour time is reached
    // - if set to a number > 1, some rotated files will be compressed using the
    // SynLZ algorithm, and will be named e.g. as MainLogFileName.0.synlz ..
    // MainLogFileName.7.synlz for RotateFileCount=9 (total count = 9, including
    // 1 main log file and 8 .synlz files)
    property RotateFileCount: cardinal read fRotateFileCount write fRotateFileCount;
    /// maximum size of auto-rotated logging files, in kilo-bytes (per 1024 bytes)
    // - specify the maximum file size upon which .synlz rotation takes place
    // - is not used if RotateFileCount is left to its default 0
    property RotateFileSizeKB: cardinal read fRotateFileSize write fRotateFileSize;
    /// fixed hour of the day where logging files rotation should be performed
    // - by default, equals -1, meaning no rotation
    // - you can set a time value between 0 and 23 to force the rotation at this
    // specified hour 
    // - is not used if RotateFileCount is left to its default 0
    property RotateFileDailyAtHour: integer read fRotateFileAtHour write fRotateFileAtHour;
    /// the recursive depth of stack trace symbol to write
    // - used only if exceptions are handled, or by sllStackTrace level
    // - default value is 20, maximum is 255
    // - if stOnlyAPI is defined as StackTraceUse under Windows XP, maximum 
    // value may be around 60, due to RtlCaptureStackBackTrace() API limitations
    property StackTraceLevel: byte read fStackTraceLevel write fStackTraceLevel;
    /// how the stack trace shall use only the Windows API
    // - the class will use low-level RtlCaptureStackBackTrace() API to retrieve
    // the call stack: in some cases, it is not able to retrieve it, therefore
    // a manual walk of the stack can be processed - since this manual call can
    // trigger some unexpected access violations or return wrong positions,
    // you can disable this optional manual walk by setting it to stOnlyAPI
    // - default is stManualAndAPI, i.e. use RtlCaptureStackBackTrace() API and
    // perform a manual stack walk if the API returned no address (or <3); but
    // within the IDE, it will use stOnlyAPI, to ensure no annoyning AV occurs
    property StackTraceUse: TSynLogStackTraceUse read fStackTraceUse write fStackTraceUse;
    /// /// how file existing shall be handled during logging
    property FileExistsAction: TSynLogExistsAction read fFileExistsAction write fFileExistsAction;
    /// define how the logger will emit its line feed
    // - by default (FALSE), a single CR (#13) char will be written, to save
    // storage space
    // - you can set this property to TRUE, so that CR+LF (#13#10) chars will
    // be appended instead
    // - TSynLogFile class and our LogView tool will handle both patterns
    property EndOfLineCRLF: boolean read fEndOfLineCRLF write fEndOfLineCRLF;
  end;

  /// thread-specific internal context used during logging
  // - this structure is a hashed-per-thread variable
  TSynLogThreadContext = record
    /// the corresponding Thread ID
    ID: cardinal;
    /// number of items stored in Recursion[]
    RecursionCount: integer;
    /// number of items available in Recursion[]
    // - faster than length(Recursion)
    RecursionCapacity: integer;
    /// used by TSynLog.Enter methods to handle recursivity calls tracing
    Recursion: array of record
      /// associated class instance to be displayed
      Instance: TObject;
      /// associated class type to be displayed
      ClassType: TClass;
      /// method name (or message) to be displayed
      MethodName: PUTF8Char;
      /// internal reference count used at this recursion level by TSynLog._AddRef
      RefCount: integer;
      /// the caller address, ready to display stack trace dump if needed
      Caller: PtrUInt;
      /// the time stamp at enter time
      EnterTimeStamp: Int64;
      /// if the method name is local, i.e. shall not be displayed at Leave()
      MethodNameLocal: (mnAlways, mnEnter, mnLeave);
    end;
  end;
  // pointer to thread-specific context information
  PSynLogThreadContext = ^TSynLogThreadContext;

  /// a per-family and/or per-thread log file content
  // - you should create a sub class per kind of log file
  // ! TSynLogDB = class(TSynLog);
  // - the TSynLog instance won't be allocated in heap, but will share a
  // per-thread (if Family.PerThreadLog=ptOneFilePerThread) or global private
  // log file instance
  // - was very optimized for speed, if no logging is written, and even during
  // log write (using an internal TTextWriter)
  // - can use available debugging information via the TSynMapFile class, for
  // stack trace logging for exceptions, sllStackTrace, and Enter/Leave labelling
  TSynLog = class(TObject, ISynLog)
  protected
    fFamily: TSynLogFamily;
    fWriter: TTextWriter;
    fWriterClass: TTextWriterClass;
    fWriterStream: TStream;
    fThreadLock: TRTLCriticalSection;
    fThreadContext: PSynLogThreadContext;
    fThreadID: cardinal;
    fThreadIndex: integer;
    fStartTimeStamp: Int64;
    fCurrentTimeStamp: Int64;
    {$ifdef MSWINDOWS}
    fFrequencyTimeStamp: Int64;
    {$endif}
    fFileName: TFileName;
    fFileRotationSize: cardinal;
    fFileRotationNextHour: Int64;
    fThreadHash: TWordDynArray;
    fThreadContexts: array of TSynLogThreadContext;
    fThreadContextCount: integer;
    fCurrentLevel: TSynLogInfo;
    fInternalFlags: set of (logHeaderWritten);
    {$ifdef FPC}
    function QueryInterface({$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} iid : tguid;out obj) : longint;{$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
    function _AddRef : longint;{$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
    function _Release : longint;{$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
    {$else}
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    {$endif}
    class function FamilyCreate: TSynLogFamily;
    procedure DoEnterLeave(aLevel: TSynLogInfo);
    procedure CreateLogWriter; virtual;
{$ifndef DELPHI5OROLDER}
    procedure LogInternal(Level: TSynLogInfo; TextFmt: PWinAnsiChar;
      const TextArgs: array of const; Instance: TObject); overload; 
{$endif}
    procedure LogInternal(Level: TSynLogInfo; const Text: RawUTF8;
      Instance: TObject; TextTruncateAtLength: integer); overload;
    procedure LogInternal(Level: TSynLogInfo; aName: PWinAnsiChar;
     aTypeInfo: pointer; var aValue; Instance: TObject=nil); overload; 
    // any call to this method MUST call UnLock
    function LogHeaderLock(Level: TSynLogInfo): boolean;
    procedure LogTrailerUnLock(Level: TSynLogInfo); {$ifdef HASINLINE}inline;{$endif}
    procedure LogCurrentTime;
    procedure LogFileHeader; virtual;
{$ifndef DELPHI5OROLDER}
    procedure AddMemoryStats; virtual;
{$endif}
    procedure AddErrorMessage(Error: cardinal);
    procedure AddStackTrace(Stack: PPtrUInt);
    procedure ComputeFileName; virtual;
    procedure PerformRotation; virtual;
    procedure AddRecursion(aIndex: integer; aLevel: TSynLogInfo);
    procedure LockAndGetThreadContext; {$ifdef HASINLINE}inline;{$endif}
    procedure LockAndGetThreadContextInternal(ID: DWORD);
    function Instance: TSynLog;
    function ConsoleEcho(Sender: TTextWriter; Level: TSynLogInfo;
      const Text: RawUTF8): boolean; virtual;
  public
    /// intialize for a TSynLog class instance
    // - WARNING: not to be called directly! Use Enter or Add class function instead
    constructor Create(aFamily: TSynLogFamily=nil);
    /// release all memory and internal handles
    destructor Destroy; override;
    /// flush all log content to file
    // - if ForceDiskWrite is TRUE, will wait until written on disk (slow)
    procedure Flush(ForceDiskWrite: boolean);
    /// flush all log content to file and close the file
    procedure CloseLogFile;
    /// flush all log content to file, close the file, and release the instance
    // - you should never call the Free method directly, since the instance
    // is registered in a global TObjectList and an access violation may
    // occur at application closing: you can use this Release method if you
    // are sure that you won't need this TSynLog instance any more
    // - ensure there is no pending Leave element in a stack-allocated ISynLog
    // (see below) 
    // - can be used e.g. to release the instance when finishing a thread when
    // Family.PerThreadLog=ptOneFilePerThread:
    // ! var
    // !   TThreadLogger : TSynLogClass = TSynLog;
    // !
    // ! procedure TMyThread.Execute;
    // ! var log : ISynLog;
    // ! begin
    // !   log := TThreadLogger.Enter(self);
    // ! ...
    // !   log := nil; // to force logging end of method
    // !   TThreadLogger.SynLog.Release;
    // ! end;
    procedure Release;
    {/ handle generic method enter / auto-leave tracing
     - this is the main method to be called within a procedure/function to trace:
     ! procedure TMyDB.SQLExecute(const SQL: RawUTF8);
     ! var ILog: ISynLog;
     ! begin
     !   ILog := TSynLogDB.Enter(self,'SQLExecute');
     !   // do some stuff
     !   ILog.Log(sllInfo,'SQL=%',[SQL]);
     ! end;
     - returning a ISynLog interface will allow you to have an automated
     sllLeave log created when the method is left (thanks to the hidden
     try..finally block generated by the compiler to protect the ISynLog var)
     - it is convenient to define a local variable to store the returned ISynLog
     and use it for any specific logging within the method execution 
     - if you just need to access the log inside the method block, you may
     not need any ISynLog interface variable:
     ! procedure TMyDB.SQLFlush;
     ! begin
     !   TSynLogDB.Enter(self,'SQLFlush');
     !   // do some stuff
     ! end;
     - if no Method name is supplied, it will use the caller address, and
     will write it as hexa and with full unit and symbol name, if the debugging
     information is available (i.e. if TSynMapFile retrieved the .map content):
     ! procedure TMyDB.SQLFlush;
     ! begin
     !   TSynLogDB.Enter(self);
     !   // do some stuff
     ! end;
     - note that supplying a method name is faster than using the .map content:
     if you want accurate profiling, it's better to use a method name or not to
     use a .map file - note that this method name shall be a constant, and not
     a locally computed variable, since it may trigger some random GPF at
     runtime - if it is a local variable, you can set aMethodNameLocal=true
     - if TSynLogFamily.HighResolutionTimeStamp is TRUE, high-resolution
     time stamp will be written instead of ISO 8601 date and time: this will
     allow performance profiling of the application on the customer side
     - Enter() will write the class name (and the unit name for classes with
     published properties, if TSynLogFamily.WithUnitName is true) for both
     enter (+) and leave (-) events:
      $ 20110325 19325801  +    MyDBUnit.TMyDB(004E11F4).SQLExecute
      $ 20110325 19325801 info   SQL=SELECT * FROM Table;
      $ 20110325 19325801  -    MyDBUnit.TMyDB(004E11F4).SQLExecute }
    class function Enter(aInstance: TObject=nil; aMethodName: PUTF8Char=nil;
      aMethodNameLocal: boolean=false): ISynLog; overload;
    /// retrieve the current instance of this TSynLog class
    // - to be used for direct logging, without any Enter/Leave:
    // ! TSynLogDB.Add.Log(llError,'The % statement didn't work',[SQL]);
    // - to be used for direct logging, without any Enter/Leave (one parameter
    // version - just the same as previous):
    // ! TSynLogDB.Add.Log(llError,'The % statement didn't work',SQL);
    // - is just a wrapper around Family.SynLog - the same code will work:
    // ! TSynLogDB.Family.SynLog.Log(llError,'The % statement didn't work',[SQL]);
    class function Add: TSynLog;
      {$ifdef HASINLINE}inline;{$endif}
    /// retrieve the family of this TSynLog class type
    class function Family: TSynLogFamily; overload;
    /// returns a logging class which will never log anything
    // - i.e. a TSynLog sub-class with Family.Level := []
    class function Void: TSynLogClass;
{$ifndef DELPHI5OROLDER}
    /// low-level method helper which can be called to make debugging easier
    // - log some warning message to the TSynLog family
    // - will force a manual breakpoint if tests are run from the IDE
    class procedure DebuggerNotify(const Args: array of const; Format: PWinAnsiChar=nil);
    /// call this method to add some information to the log at the specified level
    // - % = #37 indicates a string, integer, floating-point, or class parameter
    // to be appended as text (e.g. class name)
    // - $ = #36 indicates an integer to be written with 2 digits and a comma
    // - £ = #163 indicates an integer to be written with 4 digits and a comma
    // - µ = #181 indicates an integer to be written with 3 digits without any comma
    // - ¤ = #164 indicates CR+LF chars
    // - CR = #13 indicates CR+LF chars
    // - § = #167 indicates to trim last comma
    // - since some of this characters above are > #127, they are not UTF-8
    // ready, so we expect the input format to be WinAnsi, i.e. mostly English
    // text (with chars < #128) with some values to be inserted inside
    // - note that cardinal values should be type-casted to Int64() (otherwise
    // the integer mapped value will be transmitted, therefore wrongly)
    procedure Log(Level: TSynLogInfo; TextFmt: PWinAnsiChar; const TextArgs: array of const;
      aInstance: TObject=nil); overload;
    /// same as Log(Level,TextFmt,[]) but with one RawUTF8 parameter
    procedure Log(Level: TSynLogInfo; TextFmt: PWinAnsiChar; const TextArg: RawUTF8;
      aInstance: TObject=nil); overload;
    /// same as Log(Level,TextFmt,[]) but with one Int64 parameter
    procedure Log(Level: TSynLogInfo; TextFmt: PWinAnsiChar; const TextArg: Int64;
      aInstance: TObject=nil); overload;
{$endif}
    /// call this method to add some information to the log at the specified level
    // - if Instance is set and Text is not '', it will log the corresponding
    // class name and address (to be used e.g. if you didn't call TSynLog.Enter()
    // method first) - for instance
    // ! TSQLLog.Add.Log(sllDebug,'GarbageCollector',GarbageCollector);
    // will append this line to the log:
    // $ 0000000000002DB9 debug TObjectList(00425E68) GarbageCollector
    // - if Instance is set and Text is '', will behave the same as
    // Log(Level,Instance), i.e. write the Instance as JSON content
    procedure Log(Level: TSynLogInfo; const Text: RawUTF8;
      aInstance: TObject=nil; TextTruncateAtLength: integer=maxInt); overload;
    /// call this method to add the content of an object to the log at a
    // specified level
    // - this default implementation will just write the class name and its hexa
    // pointer value, and handle TList, TCollections and TStrings - for instance:
    // ! TSynLog.Add.Log(sllDebug,GarbageCollector);
    // will append this line to the log:
    // $ 20110330 10010005 debug {"TObjectList(00B1AD60)":["TObjectList(00B1AE20)","TObjectList(00B1AE80)"]}
    // - if aInstance is an Exception, it will handle its class name and Message:
    // $ 20110330 10010005 debug "EClassName(00C2129A)":"Exception message"
    // - use TSQLLog from mORMot.pas unit to add the record content, written
    // as human readable JSON
    procedure Log(Level: TSynLogInfo; aInstance: TObject); overload;
    /// call this method to add the content of most low-level types to the log
    // at a specified level
    // - this overridden implementation will write the value content,
    // written as human readable JSON: handle dynamic arrays and enumerations
    // - TSQLLog from mORMot.pas unit will be able to write
    // TObject/TSQLRecord and sets content as JSON
    procedure Log(Level: TSynLogInfo; aName: PWinAnsiChar;
      aTypeInfo: pointer; var aValue; Instance: TObject=nil); overload;
    /// call this method to add the caller address to the log at the specified level
    // - if the debugging info is available from TSynMapFile, will log the
    // unit name, associated symbol and source code line
    procedure Log(Level: TSynLogInfo); overload;
    /// call this method to add some multi-line information to the log at a
    // specified level
    // - LinesToLog content will be added, one line per one line, delimited by
    // #13#10 (CRLF)
    // - if a line starts with IgnoreWhenStartWith (already uppercase), it won't
    // be added to the log content (to be used e.g. with '--' for SQL statements)
    procedure LogLines(Level: TSynLogInfo; LinesToLog: PUTF8Char; aInstance: TObject=nil;
      const IgnoreWhenStartWith: PAnsiChar=nil);
    /// direct access to the low-level writing content
    // - should usually not be used directly, unless you ensure it is safe
    property Writer: TTextWriter read fWriter;
  published
    /// the associated logging family
    property GenericFamily: TSynLogFamily read fFamily;
    /// the associated file name containing the log
    // - this is accurate only with the default implementation of the class:
    // any child may override it with a custom logging mechanism
    property FileName: TFileName read fFileName;
  end;

  /// used by TSynLogFile to refer to a method profiling in a .log file
  // - i.e. map a sllEnter/sllLeave event in the .log file
  TSynLogFileProc = record
    /// the index of the sllEnter event in the TSynLogFile.fLevels[] array
    Index: cardinal;
    /// the associated time elapsed in this method (in micro seconds)
    // - computed from the sllLeave time difference (high resolution timer)
    Time: cardinal;
    /// the time elapsed in this method and not in nested methods
    // - computed from Time property, minus the nested calls
    ProperTime: cardinal;
  end;

  /// used by TSynLogFile to refer to global method profiling in a .log file
  // - i.e. map all sllEnter/sllLeave event in the .log file
  TSynLogFileProcDynArray = array of TSynLogFileProc;

  TSynLogFileProcArray = array[0..(MaxInt div sizeof(TSynLogFileProc))-1] of TSynLogFileProc;
  PSynLogFileProcArray = ^TSynLogFileProcArray;

  /// used by TSynLogFile.LogProcSort method
  TLogProcSortOrder = (
    soNone, soByName, soByOccurrence, soByTime, soByProperTime);
    
  /// used to parse a .log file, as created by TSynLog, into high-level data
  // - this particular TMemoryMapText class will retrieve only valid event lines
  // (i.e. will fill EventLevel[] for each line <> sllNone)
  // - Count is not the global text line numbers, but the number of valid events
  // within the file (LinePointers/Line/Strings will contain only event lines) -
  // it will not be a concern, since the .log header is parsed explicitely
  TSynLogFile = class(TMemoryMapText)
  protected
    /// map the events occurring in the .log file content
    fLevels: TSynLogInfoDynArray;
    fThreads: TWordDynArray;
    fThreadsRows: TCardinalDynArray;
    fThreadsCount: integer;
    fThreadsRowsCount: cardinal;
    fThreadMax: cardinal;
    fLineLevelOffset: cardinal;
    fLineTextOffset: cardinal;
    fLineHeaderCountToIgnore: integer;
    /// as extracted from the .log header
    fExeName, fExeVersion, fHost, fUser, fCPU, fOSDetailed, fInstanceName: RawUTF8;
    fExeDate: TDateTime;
{$ifdef MSWINDOWS}
    fOS: TWindowsVersion;
    fOSServicePack: integer;
    fWow64: boolean;
{$endif}
    fStartDateTime: TDateTime;
    /// retrieve all used event levels
    fLevelUsed: TSynLogInfos;
    /// =0 if date time resolution, >0 if high-resolution time stamp
    fFreq: Int64;
    /// used by EventDateTime() to compute date from time stamp
    fFreqPerDay: double;
    /// custom headers, to be searched as .ini content
    fHeaderLinesCount: integer;
    fHeaders: RawUTF8;
    /// method profiling data
    fLogProcCurrent: PSynLogFileProcArray;
    fLogProcCurrentCount: integer;
    fLogProcNatural: TSynLogFileProcDynArray;
    fLogProcNaturalCount: integer;
    fLogProcMerged: TSynLogFileProcDynArray;
    fLogProcMergedCount: integer;
    fLogProcIsMerged: boolean;
    fLogProcStack: array of cardinal;
    fLogProcStackCount: integer;
    fLogProcSortInternalOrder: TLogProcSortOrder;
    /// used by ProcessOneLine
    fLogLevelsTextMap: array[TSynLogInfo] of cardinal;
    procedure SetLogProcMerged(const Value: boolean);
    function GetEventText(index: integer): RawUTF8;
    /// retrieve headers + fLevels[] + fLogProcNatural[], and delete invalid fLines[]
    procedure LoadFromMap(AverageLineLength: integer=32); override;
    /// compute fLevels[] + fLogProcNatural[] for each .log line during initial reading
    procedure ProcessOneLine(LineBeg, LineEnd: PUTF8Char); override;
    /// called by LogProcSort method
    function LogProcSortComp(A,B: Integer): integer;
    procedure LogProcSortInternal(L,R: integer);
  public
    /// initialize internal structure
    constructor Create; override;
    /// returns TRUE if the supplied text is contained in the corresponding line
    function LineContains(const aUpperSearch: RawUTF8; aIndex: Integer): Boolean; override;
    /// retrieve the date and time of an event
    // - returns 0 in case of an invalid supplied index
    function EventDateTime(aIndex: integer): TDateTime;
    /// sort the LogProc[] array according to the supplied order
    procedure LogProcSort(Order: TLogProcSortOrder);
    /// return the number of matching events in the log
    function EventCount(const aSet: TSynLogInfos): integer;
    /// retrieve the level of an event
    // - is calculated by Create() constructor
    // - EventLevel[] array index is from 0 to Count-1
    property EventLevel: TSynLogInfoDynArray read fLevels;
    /// retrieve all used event levels
    // - is calculated by Create() constructor
    property EventLevelUsed: TSynLogInfos read fLevelUsed;
    /// retrieve the description text of an event
    // - returns '' if supplied index is out of range
    property EventText[index: integer]: RawUTF8 read GetEventText;
    /// retrieve all event thread IDs
    // - contains something if TSynLogFamily.PerThreadLog was ptIdentifiedInOnFile
    // - for ptMergedInOneFile (default) or ptOneFilePerThread logging process,
    // the array will be void (EventThread=nil)   
    property EventThread: TWordDynArray read fThreads;
    /// the number of threads 
    property ThreadsCount: cardinal read fThreadMax;
    /// the number of occurences of each thread ID
    property ThreadsRows: TCardinalDynArray read fThreadsRows;
    /// profiled methods information
    // - is calculated by Create() constructor
    // - will contain the sllEnter index, with the associated elapsed time
    // - number of items in the array is retrieved by the LogProcCount property
    property LogProc: PSynLogFileProcArray read fLogProcCurrent;
    /// the current sort order
    property LogProcOrder: TLogProcSortOrder read fLogProcSortInternalOrder;
    /// if the method information must be merged for the same method name
    property LogProcMerged: boolean read fLogProcIsMerged write SetLogProcMerged;
    /// all used event levels, as retrieved at log file content parsing
    property LevelUsed: TSynLogInfos read fLevelUsed;
    /// high-resolution time stamp frequence, as retrieved from log file header 
    // - equals 0 if date time resolution, >0 if high-resolution time stamp
    property Freq: Int64 read fFreq;
    /// custom headers, to be searched as .ini content
    property Headers: RawUTF8 read fHeaders;
  published
    /// the associated executable name (with path)
    // - returns e.g. 'C:\Dev\lib\SQLite3\exe\TestSQL3.exe'
    property ExecutableName: RawUTF8 read fExeName;
    /// the associated executable version
    // - returns e.g. '0.0.0.0'
    property ExecutableVersion: RawUTF8 read fExeVersion;
    /// the associated executable build date and time
    property ExecutableDate: TDateTime read fExeDate;
    /// for a library, the associated instance name (with path)
    // - returns e.g. 'C:\Dev\lib\SQLite3\exe\TestLibrary.dll'
    // - for an executable, will be left void
    property InstanceName: RawUTF8 read fInstanceName;
    /// the computer host name in which the process was running on
    property ComputerHost: RawUTF8 read fHost;
    /// the computer user name who launched the process
    property RunningUser: RawUTF8 read fUser;
    /// the computer CPU in which the process was running on
    // - returns e.g. '1*0-15-1027'
    property CPU: RawUTF8 read fCPU;
    {$ifdef MSWINDOWS}
    /// the computer Operating System in which the process was running on
    property OS: TWindowsVersion read fOS;
    /// the Operating System Service Pack number
    property ServicePack: integer read fOSServicePack;
    /// if the 32 bit process was running under WOW 64 virtual emulation
    property Wow64: boolean read fWow64;
    {$endif MSWINDOWS}
    /// the computer Operating System in which the process was running on
    // - returns e.g. '2.3=5.1.2600' for Windows XP
    property DetailedOS: RawUTF8 read fOSDetailed;
    /// the date and time at which the log file was started
    property StartDateTime: TDateTime read fStartDateTime;
    /// number of profiled methods in this .log file
    // - i.e. number of items in the LogProc[] array
    property LogProcCount: integer read fLogProcCurrentCount;
  end;

const
  /// up to 16 TSynLogFamily, i.e. TSynLog children classes can be defined
  MAX_SYNLOGFAMILY = 15;

  /// can be set to TSynLogFamily.Level in order to log all available events
  LOG_VERBOSE: TSynLogInfos = [succ(sllNone)..high(TSynLogInfo)];

  /// contains the logging levels for which stack trace should be dumped
  // - which are mainly exceptions or application errors
  LOG_STACKTRACE: TSynLogInfos = [sllLastError,sllError,sllException,sllExceptionOS];

  /// the text equivalency of each logging level, as written in the log file
  // - PCardinal(@LOG_LEVEL_TEXT[L][3])^ will be used for fast level matching
  // so text must be unique for characters [3..6] -> e.g. 'UST4'
  LOG_LEVEL_TEXT: array[TSynLogInfo] of string[7] = (
    '       ', ' info  ', ' debug ', ' trace ', ' warn  ', ' ERROR ',
    '  +    ', '  -    ',
    ' OSERR ', ' EXC   ', ' EXCOS ', ' mem   ', ' stack ', ' fail  ',
    ' SQL   ', ' cache ', ' res   ', ' DB    ', ' http  ', ' clnt  ', ' srvr  ',
    ' call  ', ' ret   ', ' auth  ',
    ' cust1 ', ' cust2 ', ' cust3 ', ' cust4 ', ' rotat ');

  /// the "magic" number used to identify .log.synlz compressed files, as
  // created by TSynLogFamily.EventArchiveSynLZ
  LOG_MAGIC = $ABA51051;

var
  /// the kind of .log file generated by TSynTestsLogged
  TSynLogTestLog: TSynLogClass = TSynLog;


/// a TSynLogArchiveEvent handler which will delete older .log files
function EventArchiveDelete(const aOldLogFileName, aDestinationPath: TFileName): boolean;

/// a TSynLogArchiveEvent handler which will compress older .log files
// using our proprietary SynLZ format
// - resulting file will have the .synlz extension and will be located
// in the aDestinationPath directory, i.e. TSynLogFamily.ArchivePath+'\log\YYYYMM\'
// - use UnSynLZ.dpr tool to uncompress it into .log textual file
// - SynLZ is much faster than zip for compression content, but proprietary
function EventArchiveSynLZ(const aOldLogFileName, aDestinationPath: TFileName): boolean;


implementation

{$ifdef FPC}
uses
  SynFPCTypInfo // small wrapper unit around FPC's TypInfo.pp
  {$ifdef Linux}
  , SynFPCLinux,BaseUnix, Unix, dynlibs
  {$endif} ;
{$endif}

{ TSynMapFile }

const
  MAGIC_MAB = $A5A5A5A5;

function MatchPattern(P,PEnd,Up: PUTF8Char; var Dest: PUTF8Char): boolean;
begin
  result := false;
  repeat
    if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
    while NormToUpperAnsi7[P^]=Up^ do begin
      inc(P);
      if P>PEnd then exit;
      inc(Up);
      if (Up^=' ') and (P^ in [#1..' ']) then begin // ignore multiple spaces in P^
        while (P<PEnd) and (P^ in [#1..' ']) do inc(P);
        inc(Up);
      end;
    end;
    if Up^=#0 then // all chars of Up^ found in P^
      break else
    if Up^<>' ' then // P^ and Up^ didn't match
      exit;
    inc(Up);
  until false;
  while (P<PEnd) and (P^=' ') do inc(P); // ignore all spaces
  result := true;
  Dest := P;
end;

procedure ReadSymbol(var R: TFileBufferReader; var A: TDynArray);
var i, n, L: integer;
    S: PSynMapSymbol;
    Addr: cardinal;
    P: PByte;
begin
  n := R.ReadVarUInt32;
  A.Count := n;
  P := R.CurrentMemory;
  if (n=0) or (P=nil) then
    exit;
  S := A.Value^;
  for i := 0 to n-1 do begin
    L := FromVarUInt32(P); // inlined R.Read(S^.Name)
    SetString(S^.Name,PAnsiChar(P),L);
    inc(P,L);
    inc(PtrUInt(S),A.ElemSize);
  end;
  S := A.Value^;
  Addr := FromVarUInt32(P);
  S^.Start := Addr;
  for i := 1 to n-1 do begin
    inc(Addr,FromVarUInt32(P));
    S^.Stop := Addr-1;
    inc(PtrUInt(S),A.ElemSize);
    S^.Start := Addr;
  end;
  S^.Stop := Addr+FromVarUInt32(P);
  R.Seek(PtrUInt(P)-PtrUInt(R.MappedBuffer));
end;

const
  /// Delphi linker starts the code section at this fixed offset
  CODE_SECTION = $1000;

{$ifdef UNICODE}
{ due to a bug in Delphi 2009+, we need to fake inheritance of record,
  since TDynArrayHashed = object(TDynArray) fails to initialize
  http://blog.synopse.info/post/2011/01/29/record-and-object-issue-in-Delphi-2010 }
{$define UNDIRECTDYNARRAY}
{$endif}

constructor TSynMapFile.Create(const aExeName: TFileName=''; MabCreate: boolean=true);

  procedure LoadMap;
    var P, PEnd: PUTF8Char;
    procedure NextLine;
    begin
      while (P<PEnd) and (P^>=' ') do inc(P);
      if (P<PEnd) and (P^=#13) then inc(P);
      if (P<PEnd) and (P^=#10) then inc(P);
    end;
    function GetCode(var Ptr: cardinal): boolean;
    begin
      while (P<PEnd) and (P^=' ') do inc(P);
      result := false;
      if (P+10<PEnd) and
         (PInteger(P)^=ord('0')+ord('0')shl 8+ord('0')shl 16+ord('1')shl 24) and
         (P[4]=':') then begin
        if not HexDisplayToBin(PAnsiChar(P)+5,@Ptr,sizeof(Ptr)) then exit;
        while (P<PEnd) and (P^>' ') do inc(P);
        while (P<PEnd) and (P^=' ') do inc(P);
        if P<PEnd then
          result := true;
      end;
    end;
    procedure ReadSegments;
    var Beg: PAnsiChar;
        U: TSynMapUnit;
    begin
      NextLine;
      NextLine;
      while (P<PEnd) and (P^<' ') do inc(P);
      while (P+10<PEnd) and (P^>=' ') do begin
        if GetCode(U.Symbol.Start) and
           HexDisplayToBin(PAnsiChar(P),@U.Symbol.Stop,4) then begin
          while PWord(P)^<>ord('M')+ord('=')shl 8 do
            if P+10>PEnd then exit else inc(P);
          Beg := pointer(P+2);
          while (P<PEnd) and (P^>' ') do inc(P);
          SetString(U.Symbol.Name,Beg,P-Beg);
          inc(U.Symbol.Stop,U.Symbol.Start-1);
          if (U.Symbol.Name<>'') and
             ((U.Symbol.Start<>0) or (U.Symbol.Stop<>0)) then
            fUnits.FindHashedAndUpdate(U,true); // true for adding
        end;
        NextLine;
      end;
    end;
    procedure ReadSymbols;
    var Beg: PAnsiChar;
        Sym: TSynMapSymbol;
    begin
      NextLine;
      NextLine;
      while (P+10<PEnd) and (P^>=' ') do begin
        if GetCode(Sym.Start) then begin
          while (P<PEnd) and (P^=' ') do inc(P);
          Beg := pointer(P);
          {$ifdef HASINLINE}
          // trim left 'UnitName.' for each symbol (since Delphi 2005)
          case PWord(P)^ of
          ord('S')+ord('y') shl 8:
             if IdemPChar(P+2,'STEM.') then
               if IdemPChar(P+7,'WIN.') then inc(P,9) else
               if IdemPChar(P+7,'RTTI.') then inc(P,10) else
               if IdemPChar(P+7,'TYPES.') then inc(P,10) else
               if IdemPChar(P+7,'ZLIB.') then inc(P,10) else
               if IdemPChar(P+7,'CLASSES.') then inc(P,10) else
               if IdemPChar(P+7,'SYSUTILS.') then inc(P,10) else
               if IdemPChar(P+7,'VARUTILS.') then inc(P,10) else
               if IdemPChar(P+7,'STRUTILS.') then inc(P,10) else
               if IdemPChar(P+7,'SYNCOBJS.') then inc(P,10) else
               if IdemPChar(P+7,'GENERICS.') then inc(P,16) else
               if IdemPChar(P+7,'CHARACTER.') then inc(P,10) else
               if IdemPChar(P+7,'TYPINFO.') then inc(P,10) else
               if IdemPChar(P+7,'VARIANTS.') then inc(P,10);
          ord('W')+ord('i') shl 8: if IdemPChar(P+2,'NAPI.') then inc(P,7);
          ord('V')+ord('c') shl 8: if IdemPChar(P+2,'L.') then inc(P,7);
          end;
          while (P<PEnd) and (P^<>'.') do if P^<=' ' then break else inc(P);
          if P^='.' then begin
            while (P<PEnd) and (P^='.') do inc(P);
            Beg := pointer(P);
          end else
            P := pointer(Beg); // no '.' found
          {$endif}
          while (P<PEnd) and (P^>' ') do inc(P);
          SetString(Sym.Name,Beg,P-Beg);
          if (Sym.Name<>'') and not (Sym.Name[1] in ['$','?']) then
            fSymbols.Add(Sym);
        end;
        NextLine;
      end;
    end;
    procedure ReadLines;
    var Beg: PAnsiChar;
        i, Count, n: integer;
        aName: RawUTF8;
        added: boolean;
        U: ^TSynMapUnit;
    begin
      Beg := pointer(P);
      while P^<>'(' do if P=PEnd then exit else inc(P);
      SetString(aName,Beg,P-Beg);
      if aName='' then
        exit;
      i := fUnits.FindHashedForAdding(aName,added);
      U := @fUnit[i];
      if added then
        U^.Symbol.Name := aName; // should not occur, but who knows...
      if U^.FileName='' then begin
        inc(P); Beg := pointer(P);
        while P^<>')' do if P=PEnd then exit else inc(P);
        SetString(U^.FileName,Beg,P-Beg);
      end;
      NextLine;
      NextLine;
      n := length(U^.Line);
      Count := n; // same unit may appear multiple times in .map content
      while (P+10<PEnd) and (P^>=' ') do begin
        while (P<PEnd) and (P^=' ') do inc(P);
        repeat
          if Count=n then begin
            n := Count+256;
            SetLength(U^.Line,n);
            SetLength(U^.Addr,n);
          end;
          U^.Line[Count] := GetNextItemCardinal(P,' ');
          if not GetCode(cardinal(U^.Addr[Count])) then
            break;
          if U^.Addr[Count]<>0 then
            inc(Count); // occured with Delphi 2010 :(
        until (P>=PEnd) or (P^<' ');
        NextLine;
      end;
      SetLength(U^.Line,Count);
      SetLength(U^.Addr,Count);
    end;
  var i, s,u: integer;
      RehashNeeded: boolean;
  begin // LoadMap
    fSymbols.Capacity := 8000;
    with TSynMemoryStreamMapped.Create(fMapFile) do
    try
      // parse .map sections into fSymbol[] and fUnit[]
      P := Memory;
      PEnd := P+Size;
      while P<PEnd do
        if MatchPattern(P,PEnd,'DETAILED MAP OF SEGMENTS',P) then
          ReadSegments else
        if MatchPattern(P,PEnd,'ADDRESS PUBLICS BY VALUE',P) then
          ReadSymbols else
        if MatchPattern(P,PEnd,'LINE NUMBERS FOR',P) then
          ReadLines else
         NextLine;
      // now we should have read all .map content
      s := fSymbols.Count-1;
      RehashNeeded := false;
      for i := fUnits.Count-1 downto 0 do
        with fUnit[i] do
          if (Symbol.Start=0) and (Symbol.Stop=0) then begin
            fUnits.Delete(i); // occurs with Delphi 2010 :(
            RehashNeeded := true;
          end;
      u := fUnits.Count-1;
      if RehashNeeded then
        fUnits.ReHash; // as expected by TDynArrayHashed
      {$ifopt C+}
      for i := 1 to u do
         assert(fUnit[i].Symbol.Start>fUnit[i-1].Symbol.Stop);
      {$endif}
      for i := 0 to s-1 do
        fSymbol[i].Stop := fSymbol[i+1].Start-1;
      if (u>=0) and (s>=0) then
        fSymbol[s].Stop := fUnit[u].Symbol.Stop;
    finally
      Free;
    end;
  end;

  procedure LoadMab(const aMabFile: TFileName);
  var R: TFileBufferReader;
      i: integer;
      S: TCustomMemoryStream;
      MS: TMemoryStream;
  begin
    fMapFile := aMabFile;
    if FileExists(aMabfile) then
    try
      S := TSynMemoryStreamMapped.Create(aMabFile);
      try
        MS := StreamUnSynLZ(S,MAGIC_MAB);
        if MS<>nil then
        try
          R.OpenFrom(MS.Memory,MS.Size);
          ReadSymbol(R,fSymbols);
          ReadSymbol(R,fUnits{$ifdef UNDIRECTDYNARRAY}.InternalDynArray{$endif});
          fUnits.ReHash;
          for i := 0 to fUnits.Count-1 do
          with fUnit[i] do begin
            R.Read(FileName);
            R.ReadVarUInt32Array(Line);
            R.ReadVarUInt32Array(Addr);
          end;
          MabCreate := false;
        finally
          MS.Free;
        end;
      finally
        S.Free;
      end;
    except
      on Exception do; // invalid file -> ignore any problem
    end;
  end;

var SymCount, UnitCount, i: integer;
    MabFile: TFileName;
    MapAge, MabAge: TDateTime;
begin
  fSymbols.Init(TypeInfo(TSynMapSymbolDynArray),fSymbol,@SymCount);
  fUnits.Init(TypeInfo(TSynMapUnitDynArray),fUnit,nil,nil,nil,@UnitCount);
  // 1. search for an external .map file matching the running .exe/.dll name
  if aExeName='' then begin
    fMapFile := GetModuleName(hInstance);
    {$ifdef MSWINDOWS}
    fGetModuleHandle := GetModuleHandle(pointer(
    {$else}
    fGetModuleHandle := LoadLibrary(PChar(
    {$endif}
      ExtractFileName(fMapFile)))+CODE_SECTION;
  end else
    fMapFile := aExeName;
  fMapFile := ChangeFileExt(fMapFile,'.map');
  MabFile := ChangeFileExt(fMapFile,'.mab');
  GlobalLock;
  try
    MapAge := FileAgeToDateTime(fMapFile);
    MabAge := FileAgeToDateTime(MabFile);
    if (MabAge<=MapAge) and (MapAge>0) then
      LoadMap; // if no faster-to-load .mab available and accurate
    // 2. search for a .mab file matching the running .exe/.dll name
    if (SymCount=0) and (MabAge<>0) then
      LoadMab(MabFile);
    // 3. search for an embedded compressed .mab file appended to the .exe/.dll
    if SymCount=0 then
      LoadMab(GetModuleName(hInstance));
    // finalize symbols
    if SymCount>0 then begin
      for i := 1 to SymCount-1 do
        assert(fSymbol[i].Start>fSymbol[i-1].Stop);
      SetLength(fSymbol,SymCount);
      SetLength(fUnit,UnitCount);
      fSymbols.Init(TypeInfo(TSynMapSymbolDynArray),fSymbol);
      fUnits.Init(TypeInfo(TSynMapUnitDynArray),fUnit);
      if MabCreate then
        SaveToFile(MabFile); // if just created from .map -> create .mab file
      fHasDebugInfo := true;
    end else
      fMapFile := '';
  finally
    GlobalUnLock;
  end;
end;

procedure WriteSymbol(var W: TFileBufferWriter; const A: TDynArray);
var i, n: integer;
    Diff: cardinal;
    S: PSynMapSymbol;
    P: PByte;
    Beg: PtrUInt;
begin
  n := A.Count;
  W.WriteVarUInt32(n);
  if n=0 then exit;
  S := A.Value^;
  for i := 0 to n-1 do begin
    W.Write(S^.Name);
    inc(PtrUInt(S),A.ElemSize);
  end;
  S := A.Value^;
  Diff := S^.Start;
  W.WriteVarUInt32(Diff);
  P := W.WriteDirectStart(n*5,A.ArrayTypeName); // 1 MB should be enough
  Beg := PtrUInt(P);
  for i := 1 to n-1 do begin
    inc(PtrUInt(S),A.ElemSize);
    P := ToVarUInt32(S^.Start-Diff,P);
    Diff := S^.Start;
  end;
  P := ToVarUInt32(S^.Stop-Diff,P);
  W.WriteDirectEnd(PtrUInt(P)-Beg);
end;

procedure TSynMapFile.SaveToStream(aStream: TStream);
var W: TFileBufferWriter;
    i: integer;
    MS: TMemoryStream;
begin
  MS := THeapMemoryStream.Create;
  W := TFileBufferWriter.Create(MS,1 shl 20); // 1 MB should be enough
  try
    WriteSymbol(W,fSymbols);
    WriteSymbol(W,fUnits{$ifdef UNDIRECTDYNARRAY}.InternalDynArray{$endif});
    for i := 0 to high(fUnit) do
    with fUnit[i] do begin
      W.Write(FileName);
      W.WriteVarUInt32Array(Line,length(Line),wkOffsetI); // not always increasing
      W.WriteVarUInt32Array(Addr,length(Addr),wkOffsetU); // always increasing
    end;
    W.Flush;
    StreamSynLZ(MS,aStream,MAGIC_MAB);
  finally
    MS.Free;
    W.Free;
  end;
end;

function TSynMapFile.SaveToFile(const aFileName: TFileName=''): TFileName;
var F: TFileStream;
begin
  if aFileName='' then
    result := ChangeFileExt(GetModuleName(hInstance),'.mab') else
    result := aFileName;
  DeleteFile(result);
  F := TFileStream.Create(result,fmCreate);
  try
    SaveToStream(F);
  finally
    F.Free;
  end;
end;

procedure TSynMapFile.SaveToExe(const aExeName: TFileName);
var FN: TFileName;
    MS, MAB: TMemoryStream;
    Len, LenMAB: PtrUInt;
begin
  if not FileExists(aExeName) then
    exit;
  FN := SaveToFile(ChangeFileExt(aExeName,'.mab'));
  try
    MS := THeapMemoryStream.Create;
    MAB := THeapMemoryStream.Create;
    try
      // load both files
      MAB.LoadFromFile(FN);
      LenMAB := MAB.Size;
      MS.LoadFromFile(aExeName);
      Len := MS.Size;
      if Len<16 then
        exit;
      // trim existing mab content
      Len := StreamSynLZComputeLen(MS.Memory,Len,MAGIC_MAB);
      // append mab content to exe
      MS.Size := Len+LenMAB;
      move(MAB.Memory^,PAnsiChar(MS.Memory)[Len],LenMAB);
      MS.SaveToFile(aExeName);
    finally
      MAB.Free;
      MS.Free;
    end;
  finally
    DeleteFile(FN);
  end;
end;

function TSynMapFile.FindSymbol(aAddr: cardinal): integer;
var L,R: integer;
begin
  R := high(fSymbol);
  L := 0;
  if (R>=0) and (aAddr>=fSymbol[0].Start) and (aAddr<=fSymbol[R].Stop) then
  repeat
    result := (L+R)shr 1;
    with fSymbol[result] do
      if aAddr<Start then
        R := result-1 else
      if aAddr>Stop then
        L := result+1 else
        exit;
  until L>R;
  result := -1;
end;

function TSynMapFile.FindUnit(aAddr: cardinal; out LineNumber: integer): integer;
var L,R,n,max: integer;
begin
  LineNumber := 0;
  R := high(fUnit);
  L := 0;
  if (R>=0) and
     (aAddr>=fUnit[0].Symbol.Start) and (aAddr<=fUnit[R].Symbol.Stop) then
  repeat
    result := (L+R) shr 1;
    with fUnit[result] do
      if aAddr<Symbol.Start then
        R := result-1 else
      if aAddr>Symbol.Stop then
        L := result+1 else begin
        // unit found -> search line number
        L := 0;
        max := high(Addr);
        R := max;
        if R>=0 then
        repeat
          n := (L+R) shr 1;
          if aAddr<cardinal(Addr[n]) then
            R := n-1 else
          if (n<max) and (aAddr>=cardinal(Addr[n+1])) then
            L := n+1 else begin
            LineNumber := Line[n];
            exit;
          end;
        until L>R;
        exit;
      end;
  until L>R;
  result := -1;
end;

var
  InstanceMapFile: TSynMapFile;
  
class procedure TSynMapFile.Log(W: TTextWriter; Addr: PtrUInt);
var u, s, Line: integer;
begin
  if (W=nil) or (Addr=0) or (InstanceMapFile=nil) then
    exit;
  with InstanceMapFile do
  if HasDebugInfo then begin
    dec(Addr,fGetModuleHandle);
    s := FindSymbol(Addr);
    u := FindUnit(Addr,Line);
    if s<0 then begin
      if u<0 then
        exit;
    end else
      with PInt64Rec(Symbols[s].Name)^ do
      if Lo=ord('T')+ord('S')shl 8+ord('y')shl 16+ord('n')shl 24 then
      case Hi of
        ord('L')+ord('o')shl 8+ord('g')shl 16+ord('.')shl 24,
        ord('T')+ord('e')shl 8+ord('s')shl 16+ord('t')shl 24:
          exit; // don't log stack trace internal to TSynLog.*/TSynTest* methods
      end;
    W.AddPointer(Addr); // only display addresses inside known Delphi code
    W.Add(' ');
    if u>=0 then begin
      W.AddString(Units[u].Symbol.Name);
      if s>=0 then
        if Symbols[s].Name=Units[u].Symbol.Name then
          s := -1 else
          W.Add('.');
    end;
    if s>=0 then
      W.AddString(Symbols[s].Name);
    W.Add(' ');
    if Line>0 then begin
      W.Add('(');
      W.Add(Line);
      W.Add(')',' ');
    end;
  end else begin
    W.AddPointer(Addr); // no .map info available -> display all addresses
    W.Add(' ');
  end;
end;

function TSynMapFile.FindLocation(aAddr: Cardinal): RawUTF8;
var u,s,Line: integer;
begin
  result := '';
  if (aAddr=0) or not HasDebugInfo then
    exit;
  dec(aAddr,fGetModuleHandle);
  s := FindSymbol(aAddr);
  u := FindUnit(aAddr,Line);
  if (s<0) and (u<0) then
    exit;
  if u>=0 then begin
    with Units[u] do begin
      if FileName<>'' then
        result := FileName+' - ';
      result := result+Symbol.Name;
    end;
    if s>=0 then
      if Symbols[s].Name=Units[u].Symbol.Name then
        s := -1 else
        result := result+'.';
  end;
  if s>=0 then
    result := result+Symbols[s].Name;
  if Line>0 then
    result := result+' ('+Int32ToUtf8(Line)+')';
end;


{ TSynLogFamily }

type
  /// an array to all available per-thread TSynLogFile instances
  TSynLogFileIndex = array[0..MAX_SYNLOGFAMILY] of integer;

var
  /// internal list of registered TSynLogFamily
  // - up to MAX_SYNLOGFAMILY+1 families may be defined
  SynLogFamily: TObjectList = nil;

  /// internal list of created TSynLog instance, one per each log file on disk
  // - do not use directly - necessary for inlining TSynLogFamily.SynLog method
  // - also used by AutoFlushProc() to get a global list of TSynLog instances
  SynLogFileList: TObjectListLocked = nil;

threadvar
  /// each thread can access to its own TSynLogFile
  // - is used to implement TSynLogFamily.PerThreadLog=ptOneFilePerThread option
  // - the current TSynLogFile instance of the living thread is
  // ! SynLogFileList[SynLogFileIndexThreadVar[TSynLogFamily.Ident]-1]
  SynLogFileIndexThreadVar: TSynLogFileIndex;

/// if defined, will use AddVectoredExceptionHandler() API call
// - this one does not produce accurate stack trace by now, and is supported
// only since Windows XP
// - so default method using RTLUnwindProc should be prefered
{.$define WITH_VECTOREXCEPT}

{$ifdef CPU64}
  {$define WITH_VECTOREXCEPT}
{$endif}

{$ifdef DELPHI5OROLDER}
// Delphi 5 doesn't define the needed RTLUnwindProc variable :(
// so we will patch the System.pas RTL in-place

var
  RTLUnwindProc: Pointer;

procedure PatchCallRtlUnWind;
procedure Patch(P: PAnsiChar);
{   004038B6 52               push edx  // Save exception object
    004038B7 51               push ecx  // Save exception address
    004038B8 8B542428         mov edx,[esp+$28]
    004038BC 83480402         or dword ptr [eax+$04],$02
    004038C0 56               push esi  // Save handler entry
    004038C1 6A00             push $00
    004038C3 50               push eax
    004038C4 68CF384000       push $004038cf  // @@returnAddress
    004038C9 52               push edx
    004038CA E88DD8FFFF       call RtlUnwind
    ...
RtlUnwind:
    0040115C FF255CC14100     jmp dword ptr [$0041c15c]
    where $0041c15c is a pointer to the address of RtlUnWind in kernel32.dll
    -> we will replace [$0041c15c] by [RTLUnwindProc]    }
var i: Integer;
    addr: PAnsiChar;
begin
  for i := 0 to 31 do
    if (PCardinal(P)^=$6850006a) and  // push 0; push eax; push @@returnAddress
       (PWord(P+8)^=$E852) then begin // push edx; call RtlUnwind
      inc(P,10); // go to call RtlUnwind address
      if PInteger(P)^<0 then begin
        addr := P+4+PInteger(P)^;
        if PWord(addr)^=$25FF then begin // jmp dword ptr []
          PatchCodePtrUInt(Pointer(addr+2),cardinal(@RTLUnwindProc));
          exit;
        end;
      end;
    end else
    inc(P);
end;
asm
  mov eax,offset System.@HandleAnyException+200
  call Patch
end;
{$endif DELPHI5OROLDER}

{$ifndef NOEXCEPTIONINTERCEPT}

var
  /// used internaly by function GetHandleExceptionSynLog
  CurrentHandleExceptionSynLog: TSynLog;

function GetHandleExceptionSynLog: TSynLog;
var Index: ^TSynLogFileIndex;
    i: integer;
    ndx, n: cardinal;
begin
  result := CurrentHandleExceptionSynLog;
  if (result<>nil) and result.fFamily.fHandleExceptions then
    exit;
  if SynLogFileList=nil then begin
    // we are here if no log content was generated yet (i.e. no log file yet)
    for i := 0 to SynLogFamily.Count-1 do
      with TSynLogFamily(SynLogFamily.List[i]) do
      if fHandleExceptions then begin
        result := SynLog;
        exit;
      end;
  end else begin
    SynLogFileList.Lock;
    try
      Index := @SynLogFileIndexThreadVar;
      n := SynLogFileList.Count;
      for i := 0 to high(Index^) do begin
        ndx := Index^[i]-1;
        if ndx<=n then begin
          result := TSynLog(SynLogFileList.List[ndx]);
          if result.fFamily.fHandleExceptions then
            exit;
        end;
      end;
      for i := 0 to n-1 do begin
        result := TSynLog(SynLogFileList.List[i]);
        if result.fFamily.fHandleExceptions then
          exit;
      end;
    finally
      SynLogFileList.UnLock;
    end;
  end;
  result := nil;
end;

type
  PExceptionRecord = ^TExceptionRecord;
  TExceptionRecord = record
    ExceptionCode: DWord;
    ExceptionFlags: DWord;
    OuterException: PExceptionRecord;
    ExceptionAddress: PtrUInt;
    NumberParameters: Longint;
    case {IsOsException:} Boolean of
    True:  (ExceptionInformation : array [0..14] of PtrUInt);
    False: (ExceptAddr: PtrUInt; ExceptObject: Exception);
  end;
  GetExceptionClass = function(const P: TExceptionRecord): ExceptClass;

const
  cDelphiExcept = $0EEDFAE0;
  cDelphiException = $0EEDFADE;
  // see http://msdn.microsoft.com/en-us/library/xcb2z8hs 
  cSetThreadNameException = $406D1388;

  DOTNET_EXCEPTIONNAME: array[0..83] of RawUTF8 = (
  'Access', 'AmbiguousMatch', 'appdomainUnloaded', 'Application', 'Argument',
  'ArgumentNull', 'ArgumentOutOfRange', 'Arithmetic', 'ArrayTypeMismatch',
  'BadImageFormat', 'CannotUnloadappdomain', 'ContextMarshal', 'Cryptographic',
  'CryptographicUnexpectedOperation', 'CustomAttributeFormat', 'DirectoryNotFound',
  'DirectoryNotFound', 'DivideByZero', 'DllNotFound', 'DuplicateWaitObject',
  'EndOfStream', 'EntryPointNotFound', '', 'ExecutionEngine', 'External',
  'FieldAccess', 'FileLoad', 'FileLoad', 'FileNotFound', 'Format',
  'IndexOutOfRange', 'InvalidCast', 'InvalidComObject', 'InvalidFilterCriteria',
  'InvalidOleVariantType', 'InvalidOperation', 'InvalidProgram', 'IO',
  'IsolatedStorage', 'MarshalDirective', 'MethodAccess', 'MissingField',
  'MissingManifestResource', 'MissingMember', 'MissingMethod',
  'MulticastNotSupported', 'NotFiniteNumber', 'NotImplemented', 'NotSupported',
  'NullReference', 'OutOfMemory', 'Overflow', 'PlatformNotSupported', 'Policy',
  'Rank', 'ReflectionTypeLoad', 'Remoting', 'RemotingTimeout', 'SafeArrayTypeMismatch',
  'SafeArrayRankMismatch', 'Security', 'SEH', 'Serialization', 'Server', 'StackOverflow',
  'SUDSGenerator', 'SUDSParser', 'SynchronizationLock', 'System', 'Target',
  'TargetInvocation', 'TargetParameterCount', 'ThreadAbort', 'ThreadInterrupted',
  'ThreadState', 'ThreadStop', 'TypeInitialization', 'TypeLoad', 'TypeUnloaded',
  'UnauthorizedAccess', 'InClassConstructor', 'KeyNotFound', 'InsufficientStack',
  'InsufficientMemory');
  // http://blogs.msdn.com/b/yizhang/archive/2010/12/17/interpreting-hresults-returned-from-net-clr-0x8013xxxx.aspx
  DOTNET_EXCEPTIONHRESULT: array[0..83] of cardinal = (
   $8013151A, $8000211D, $80131015, $80131600, $80070057, $80004003, $80131502,
   $80070216, $80131503, $8007000B, $80131015, $80090020, $80004001, $80131431,
   $80131537, $80070003, $80030003, $80020012, $80131524, $80131529, $801338,
   $80131522, $80131500, $80131506, $80004005, $80131507, $80131621, $80131018,
   $80070002, $80131537, $80131508, $80004002, $80131527, $80131601, $80131531,
   $80131509, $8013153A, $80131620, $80131450, $80131535, $80131510, $80131511,
   $80131532, $80131512, $80131513, $80131514, $80131528, $80004001, $80131515,
   $80004003, $8007000E, $80131516, $80131539, $80131416, $80131517,
   $80131602, $8013150B, $8013150B, $80131533, $80131538, $8013150A, $80004005,
   $8013150C, $8013150E, $800703E9, $80131500, $80131500, $80131518, $80131501,
   $80131603, $80131604, $80138002, $80131530, $80131519, $80131520, $80131521,
   $80131534, $80131522, $80131013, $80070005, $80131543, $80131577, $80131578,
   $8013153D);

type
  // avoid linking of ComObj.pas just for EOleSysError
  EOleSysError = class(Exception)
  public
    ErrorCode: cardinal;
  end;

function ExceptionInheritsFrom(E: TClass; const Name: ShortString): boolean;
begin // avoid linking of ComObj.pas just for EOleSysError
  while (E<>nil) and (E<>Exception) do
    if IdemPropName(PShortString(PPointer(PtrInt(E)+vmtClassName)^)^,Name) then begin
      result := true;
      exit;
    end else begin
      E := PPointer(PtrInt(E)+vmtParent)^;
      if E<>nil then
        E := PPointer(E)^;
    end;
  result := false;
end;

function InternalDefaultSynLogExceptionToStr(
  WR: TTextWriter; const Context: TSynLogExceptionContext): boolean;
var i: integer;
begin
  WR.AddClassName(Context.EClass);
  if (Context.Level=sllException) and (Context.EInstance<>nil) and
     (Context.EClass<>EExternalException) then begin
    if ExceptionInheritsFrom(Context.EClass,'EOleSysError') then begin
      WR.Add(' ');
      WR.AddPointer(EOleSysError(Context.EInstance).ErrorCode);
      for i := 0 to high(DOTNET_EXCEPTIONHRESULT) do
        if DOTNET_EXCEPTIONHRESULT[i]=EOleSysError(Context.EInstance).ErrorCode then begin
          WR.AddShort(' [.NET/CLR unhandled ');
          WR.AddString(DOTNET_EXCEPTIONNAME[i]);
          WR.AddShort('Exception]');
        end; // no break on purpose, if ErrorCode matches more than one Exception
    end;
    WR.AddShort(' ("');
    WR.AddJSONEscapeString(Context.EInstance.Message);
    WR.AddShort('")');
  end else begin
    WR.AddShort(' (');
    WR.AddPointer(Context.ECode);
    WR.AddShort(')');
  end;
  result := false; // caller should append "at EAddr" and the stack trace
end;

procedure LogExcept(stack: PPtrUInt; const Exc: TExceptionRecord);
var SynLog: TSynLog;
    Ctxt: TSynLogExceptionContext;
    LastError: DWORD;
begin
  if Exc.ExceptionCode=cSetThreadNameException then
    exit;
  SynLog := GetHandleExceptionSynLog;
  if SynLog=nil then
    exit;
  LastError := GetLastError;
  if (Exc.ExceptionCode=cDelphiException) and (Exc.ExceptObject<>nil) then begin
    if Exc.ExceptObject.InheritsFrom(Exception) then
      Ctxt.EClass := PPointer(Exc.ExceptObject)^ else
      Ctxt.EClass := EExternalException;
    if sllException in SynLog.fFamily.Level then
      Ctxt.Level := sllException else
      Ctxt.Level := sllError;
    Ctxt.EAddr := Exc.ExceptAddr;
  end else begin
    {$ifdef MSWINDOWS}
    if Assigned(ExceptClsProc) then
      Ctxt.EClass := GetExceptionClass(ExceptClsProc)(Exc) else
    {$endif}
      Ctxt.EClass := EExternal;
    Ctxt.Level := sllExceptionOS;
    Ctxt.EAddr := Exc.ExceptionAddress;
  end;
  if (Ctxt.Level<>sllError) and
     (SynLog.fFamily.ExceptionIgnore.IndexOf(Ctxt.EClass)<0) then begin
    if SynLog.LogHeaderLock(Ctxt.Level) then
    try
      Ctxt.EInstance := Exc.ExceptObject;
      Ctxt.ECode := Exc.ExceptionCode;
      repeat
        if (Ctxt.Level=sllException) and (Exc.ExceptionCode=cDelphiException) and
           Ctxt.EInstance.InheritsFrom(ESynException) then begin
          if ESynException(Ctxt.EInstance).CustomLog(SynLog.fWriter,Ctxt) then
            break;
        end else
        if Assigned(TSynLogExceptionToStrCustom) then begin
          if TSynLogExceptionToStrCustom(SynLog.fWriter,Ctxt) then
            break;
        end else
          if DefaultSynLogExceptionToStr(SynLog.fWriter,Ctxt) then
            break;
        SynLog.fWriter.AddShort(' at ');
        TSynMapFile.Log(SynLog.fWriter,Ctxt.EAddr);
  {$ifndef WITH_VECTOREXCEPT} // stack frame is only correct for RTLUnwindProc
        SynLog.AddStackTrace(stack);
  {$endif}
        break;
      until false;
      SynLog.fWriter.AddEndOfLine(SynLog.fCurrentLevel);
      SynLog.fWriter.Flush; // we expect exceptions to be available on disk
    finally
      LeaveCriticalSection(SynLog.fThreadLock);
    end;
  end;
  SetLastError(LastError); // code above could have changed this
end;


{$ifdef WITH_VECTOREXCEPT}
type
  PExceptionInfo = ^TExceptionInfo;
  TExceptionInfo = packed record
    ExceptionRecord: PExceptionRecord;
    ContextRecord: pointer;
  end;

var
  AddVectoredExceptionHandler: function(FirstHandler: cardinal;
    VectoredHandler: pointer): PtrInt; stdcall;

function SynLogVectoredHandler(ExceptionInfo : PExceptionInfo): PtrInt; stdcall;
const
  EXCEPTION_CONTINUE_SEARCH = 0;
begin
  if CurrentHandleExceptionSynLog<>nil then
    LogExcept(nil,ExceptionInfo^.ExceptionRecord^);
  result := EXCEPTION_CONTINUE_SEARCH;
end;

{$else WITH_VECTOREXCEPT}

{$ifdef DELPHI5OROLDER}
procedure RtlUnwind; external kernel32 name 'RtlUnwind';
{$else}
var
  oldUnWindProc: pointer;
{$endif DELPHI5OROLDER}

procedure SynRtlUnwind(TargetFrame, TargetIp: pointer;
  ExceptionRecord: PExceptionRecord; ReturnValue: Pointer); stdcall;
asm
  pushad
  cmp  dword ptr CurrentHandleExceptionSynLog,0
  jz   @oldproc
  mov  eax,TargetFrame
  mov  edx,ExceptionRecord
  call LogExcept
@oldproc:
  popad
  pop ebp // hidden push ebp at asm level
{$ifdef DELPHI5OROLDER}
  jmp RtlUnwind
{$else}
  jmp oldUnWindProc
{$endif}
end;

{$endif WITH_VECTOREXCEPT}

{$endif NOEXCEPTIONINTERCEPT}

procedure TSynLogFamily.SetDestinationPath(const value: TFileName);
begin
  fDestinationPath := IncludeTrailingPathDelimiter(value);
end;

procedure TSynLogFamily.SetLevel(aLevel: TSynLogInfos);
begin
  // ensure BOTH Enter+Leave are always selected at once, if any is set
  if sllEnter in aLevel then
    include(aLevel,sllLeave) else
  if sllLeave in aLevel then
    include(aLevel,sllEnter);
  fLevel := aLevel;
{$ifndef NOEXCEPTIONINTERCEPT}
  // intercept exceptions, if necessary
  fHandleExceptions := (sllExceptionOS in aLevel) or (sllException in aLevel);
  if fHandleExceptions and (CurrentHandleExceptionSynLog=nil) then begin
    SynLog; // force define CurrentHandleExceptionSynLog
  {$ifdef WITH_VECTOREXCEPT}
    AddVectoredExceptionHandler :=
      GetProcAddress(GetModuleHandle(kernel32),'AddVectoredExceptionHandler');
    // RemoveVectoredContinueHandler() is available under 64 bit editions only
    if Assigned(AddVectoredExceptionHandler) then
      // available since Windows XP
      AddVectoredExceptionHandler(0,@SynLogVectoredHandler);
  {$else WITH_VECTOREXCEPT}
  {$ifdef DELPHI5OROLDER}
    PatchCallRtlUnWind;
  {$else}
    oldUnWindProc := RTLUnwindProc;
  {$endif}
    RTLUnwindProc := @SynRtlUnwind;
  {$endif WITH_VECTOREXCEPT}
  end;
{$endif NOEXCEPTIONINTERCEPT}
end;

procedure TSynLogFamily.SetEchoToConsole(aEnabled: TSynLogInfos);
begin
  if (self=nil) or (aEnabled=fEchoToConsole) then
    exit;
  fEchoToConsole := aEnabled;
end;

constructor TSynLogFamily.Create(aSynLog: TSynLogClass);
begin
  fSynLogClass := aSynLog;
  if SynLogFamily=nil then
    GarbageCollectorFreeAndNil(SynLogFamily,TList.Create);
  fIdent := SynLogFamily.Add(self);
  {$ifdef MSWINDOWS}
  fDestinationPath := ExtractFilePath(paramstr(0)); // use .exe path
  {$endif}
  fDefaultExtension := '.log';
  fArchivePath := fDestinationPath;
  fArchiveAfterDays := 7;
  fRotateFileAtHour := -1;
  fBufferSize := 4096;
  fStackTraceLevel := 20;
  {$ifndef FPC}
  if DebugHook<>0 then // never let stManualAndAPI trigger AV within the IDE
    fStackTraceUse := stOnlyAPI;
  {$endif}
  fExceptionIgnore := TList.Create;
  fLevelStackTrace :=
    [sllError,sllException,sllExceptionOS,sllFail,sllLastError,sllStackTrace];
end;

function TSynLogFamily.CreateSynLog: TSynLog;
var i: integer;
begin
  if SynLogFileList=nil then
    GarbageCollectorFreeAndNil(SynLogFileList,TObjectListLocked.Create);
  SynLogFileList.Lock;
  try
    result := fSynLogClass.Create(self);
    i := SynLogFileList.Add(result);
    if fPerThreadLog=ptOneFilePerThread then 
      if (fRotateFileCount=0) and (fRotateFileSize=0) and (fRotateFileAtHour<0) then 
        SynLogFileIndexThreadVar[fIdent] := i+1 else begin
        fPerThreadLog := ptIdentifiedInOnFile; // excluded by rotation
        fGlobalLog := result;
      end else
      fGlobalLog := result;
  finally
    SynLogFileList.UnLock;
  end;
end;

{$ifdef MSWINDOWS}

var
  StdOut: THandle;

var
  AutoFlushThread: THandle = 0;
  AutoFlushSecondElapsed: cardinal;

procedure AutoFlushProc(P: pointer); stdcall;  // TThread not needed here
var i: integer;
begin
  repeat
    for i := 1 to 10 do begin // check every second for pending data
      Sleep(100);
      if AutoFlushThread=0 then
        exit; // avoid GPF
    end;
    if SynLogFileList=nil then
      continue; // nothing to flush
    inc(AutoFlushSecondElapsed);
    SynLogFileList.Lock;
    try
      for i := 0 to SynLogFileList.Count-1 do
      with TSynLog(SynLogFileList.List[i]) do
        if AutoFlushThread=0 then
          break else // avoid GPF
        if (fFamily.fAutoFlush<>0) and (fWriter<>nil) and
           (AutoFlushSecondElapsed mod fFamily.fAutoFlush=0) then
          if fWriter.PendingBytes>1 then begin
            if not IsMultiThread then
              if not fWriterStream.InheritsFrom(TFileStream) then
                IsMultiThread := true; // only TFileStream is thread-safe
            Flush(false); // write pending data
          end;
     finally
       SynLogFileList.UnLock;
     end;
  until false;
  ExitThread(0);
end;

procedure TSynLogFamily.SetAutoFlush(TimeOut: cardinal);
var ID: cardinal;
begin
  fAutoFlush := TimeOut;
  if (AutoFlushThread=0) and (TimeOut<>0) {$ifndef FPC}and (DebugHook=0){$endif} then begin
    AutoFlushThread := CreateThread(nil,0,@AutoFlushProc,nil,0,ID);
    AutoFlushSecondElapsed := 0;
  end;
end;

{$endif}

destructor TSynLogFamily.Destroy;
var SR: TSearchRec;
    OldTime: integer;
    aTime: TDateTime;
    Y,M,D: word;
    aOldLogFileName, aPath: TFileName;
    tmp: array[0..11] of AnsiChar;
begin
  fDestroying := true;
  EchoRemoteStop;
  {$ifdef MSWINDOWS}
  if AutoFlushThread<>0 then
    AutoFlushThread := 0; // mark thread released to avoid GPF in AutoFlushProc
  {$endif}
  ExceptionIgnor