diff --git a/CouchbaseLite.xcodeproj/project.pbxproj b/CouchbaseLite.xcodeproj/project.pbxproj index d85c63d38..370551308 100644 --- a/CouchbaseLite.xcodeproj/project.pbxproj +++ b/CouchbaseLite.xcodeproj/project.pbxproj @@ -165,10 +165,6 @@ 1AA2EE0528A682AF00DEB47E /* CBLCollectionConfiguration+Swift.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AA2EDF028A671AD00DEB47E /* CBLCollectionConfiguration+Swift.m */; }; 1AA3D6C122A1A41E0098E16B /* ReplicatorTest+CustomConflict.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA3D6B022A1A3490098E16B /* ReplicatorTest+CustomConflict.swift */; }; 1AA3D6C222A1A41F0098E16B /* ReplicatorTest+CustomConflict.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA3D6B022A1A3490098E16B /* ReplicatorTest+CustomConflict.swift */; }; - 1AA3D78422AB07C50098E16B /* CustomLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AA3D77222AB06E10098E16B /* CustomLogger.m */; }; - 1AA3D78622AB07D50098E16B /* CustomLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AA3D77222AB06E10098E16B /* CustomLogger.m */; }; - 1AA3D78722AB07D70098E16B /* CustomLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AA3D77222AB06E10098E16B /* CustomLogger.m */; }; - 1AA3D78822AB07D80098E16B /* CustomLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AA3D77222AB06E10098E16B /* CustomLogger.m */; }; 1AA67439227924110018CC6D /* QueryTest+Meta.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AAF6371226A8B060016754C /* QueryTest+Meta.m */; }; 1AA6743A227924110018CC6D /* QueryTest+Join.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AAF6382226A8DBF0016754C /* QueryTest+Join.m */; }; 1AA6744A227924120018CC6D /* QueryTest+Meta.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AAF6371226A8B060016754C /* QueryTest+Meta.m */; }; @@ -365,6 +361,10 @@ 40086B552B803B2B00DA6770 /* CBLBlockConflictResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = 40086B522B803B2B00DA6770 /* CBLBlockConflictResolver.m */; }; 40086B572B803B4300DA6770 /* CBLBlockConflictResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = 40086B522B803B2B00DA6770 /* CBLBlockConflictResolver.m */; }; 40086B582B803B4E00DA6770 /* CBLBlockConflictResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = 40086B522B803B2B00DA6770 /* CBLBlockConflictResolver.m */; }; + 4009842B2D10F48E0029F26E /* CBLLogTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 4009842A2D10F48E0029F26E /* CBLLogTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4009842C2D10F48E0029F26E /* CBLLogTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 4009842A2D10F48E0029F26E /* CBLLogTypes.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 4009842D2D10F48E0029F26E /* CBLLogTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 4009842A2D10F48E0029F26E /* CBLLogTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4009842E2D10F48E0029F26E /* CBLLogTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 4009842A2D10F48E0029F26E /* CBLLogTypes.h */; settings = {ATTRIBUTES = (Private, ); }; }; 400AAFDB2C2A843B00DB6223 /* CBLExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = 400AAFCD2C2A843B00DB6223 /* CBLExtension.h */; settings = {ATTRIBUTES = (Public, ); }; }; 400AAFDC2C2A843B00DB6223 /* CBLExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = 400AAFCD2C2A843B00DB6223 /* CBLExtension.h */; settings = {ATTRIBUTES = (Private, ); }; }; 400AAFDD2C2A843B00DB6223 /* CBLExtension.mm in Sources */ = {isa = PBXBuildFile; fileRef = 400AAFDA2C2A843B00DB6223 /* CBLExtension.mm */; }; @@ -394,6 +394,26 @@ 406F8DFB2C27C303000223FC /* IndexUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F8DF92C27C302000223FC /* IndexUpdater.swift */; }; 406F8DFF2C27F0A9000223FC /* VectorSearchTest+Lazy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F8DFC2C27F097000223FC /* VectorSearchTest+Lazy.swift */; }; 406F8E002C27F0AB000223FC /* VectorSearchTest+Lazy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F8DFC2C27F097000223FC /* VectorSearchTest+Lazy.swift */; }; + 409389E92D4AB81700691393 /* LogSinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 409389E82D4AB81700691393 /* LogSinks.swift */; }; + 409389EA2D4AB81700691393 /* LogSinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 409389E82D4AB81700691393 /* LogSinks.swift */; }; + 409389EC2D4AB8B500691393 /* ConsoleLogSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 409389EB2D4AB8B500691393 /* ConsoleLogSink.swift */; }; + 409389ED2D4AB8B500691393 /* ConsoleLogSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 409389EB2D4AB8B500691393 /* ConsoleLogSink.swift */; }; + 409389EF2D4AB8EB00691393 /* CustomLogSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 409389EE2D4AB8EB00691393 /* CustomLogSink.swift */; }; + 409389F02D4AB8EB00691393 /* CustomLogSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 409389EE2D4AB8EB00691393 /* CustomLogSink.swift */; }; + 409389F22D4AB99900691393 /* FileLogSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 409389F12D4AB99900691393 /* FileLogSink.swift */; }; + 409389F32D4AB99900691393 /* FileLogSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 409389F12D4AB99900691393 /* FileLogSink.swift */; }; + 40938A0C2D4AFA5900691393 /* LogSinkTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40938A0B2D4AFA5900691393 /* LogSinkTest.swift */; }; + 40938A0D2D4AFA5900691393 /* LogSinkTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40938A0B2D4AFA5900691393 /* LogSinkTest.swift */; }; + 40938A0E2D4AFA5900691393 /* LogSinkTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40938A0B2D4AFA5900691393 /* LogSinkTest.swift */; }; + 40938A0F2D4AFA5900691393 /* LogSinkTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40938A0B2D4AFA5900691393 /* LogSinkTest.swift */; }; + 40938A132D4B26D000691393 /* CBLLogSinks+Reset.h in Headers */ = {isa = PBXBuildFile; fileRef = 40938A122D4B26D000691393 /* CBLLogSinks+Reset.h */; }; + 40938A142D4B26D000691393 /* CBLLogSinks+Reset.h in Headers */ = {isa = PBXBuildFile; fileRef = 40938A122D4B26D000691393 /* CBLLogSinks+Reset.h */; }; + 40938A152D4B26D000691393 /* CBLLogSinks+Reset.h in Headers */ = {isa = PBXBuildFile; fileRef = 40938A122D4B26D000691393 /* CBLLogSinks+Reset.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 40938A162D4B26D000691393 /* CBLLogSinks+Reset.h in Headers */ = {isa = PBXBuildFile; fileRef = 40938A122D4B26D000691393 /* CBLLogSinks+Reset.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 40938A512D4B464500691393 /* CBLTestCustomLogSink.m in Sources */ = {isa = PBXBuildFile; fileRef = 40938A502D4B464500691393 /* CBLTestCustomLogSink.m */; }; + 40938A522D4B464500691393 /* CBLTestCustomLogSink.m in Sources */ = {isa = PBXBuildFile; fileRef = 40938A502D4B464500691393 /* CBLTestCustomLogSink.m */; }; + 40938A532D4B464500691393 /* CBLTestCustomLogSink.m in Sources */ = {isa = PBXBuildFile; fileRef = 40938A502D4B464500691393 /* CBLTestCustomLogSink.m */; }; + 40938A542D4B464500691393 /* CBLTestCustomLogSink.m in Sources */ = {isa = PBXBuildFile; fileRef = 40938A502D4B464500691393 /* CBLTestCustomLogSink.m */; }; 40AA72952C28938D007FB1E0 /* VectorSearchTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AE006DE62B7BB98B00884E2B /* VectorSearchTest.m */; }; 40AA72962C28938E007FB1E0 /* VectorSearchTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AE006DE62B7BB98B00884E2B /* VectorSearchTest.m */; }; 40AA729D2C28B1A3007FB1E0 /* VectorSearchTest+Lazy.m in Sources */ = {isa = PBXBuildFile; fileRef = 40AA72972C28B1A3007FB1E0 /* VectorSearchTest+Lazy.m */; }; @@ -1479,10 +1499,6 @@ 9388CC3321C18672005CA66D /* CBLLog+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 93C50F0F21C068F300C7E980 /* CBLLog+Internal.h */; }; 9388CC3421C18673005CA66D /* CBLLog+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 93C50F0F21C068F300C7E980 /* CBLLog+Internal.h */; }; 9388CC3521C18674005CA66D /* CBLLog+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 93C50F0F21C068F300C7E980 /* CBLLog+Internal.h */; }; - 9388CC3621C186DD005CA66D /* CBLLog+Admin.h in Headers */ = {isa = PBXBuildFile; fileRef = 274D6C272011529D00DAB7DD /* CBLLog+Admin.h */; }; - 9388CC3721C186DE005CA66D /* CBLLog+Admin.h in Headers */ = {isa = PBXBuildFile; fileRef = 274D6C272011529D00DAB7DD /* CBLLog+Admin.h */; }; - 9388CC3821C186DF005CA66D /* CBLLog+Admin.h in Headers */ = {isa = PBXBuildFile; fileRef = 274D6C272011529D00DAB7DD /* CBLLog+Admin.h */; }; - 9388CC3921C186DF005CA66D /* CBLLog+Admin.h in Headers */ = {isa = PBXBuildFile; fileRef = 274D6C272011529D00DAB7DD /* CBLLog+Admin.h */; }; 9388CC3A21C18784005CA66D /* CBLLog.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9388CBE721BF7195005CA66D /* CBLLog.mm */; }; 9388CC3B21C18786005CA66D /* CBLLog.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9388CBE721BF7195005CA66D /* CBLLog.mm */; }; 9388CC3C21C18787005CA66D /* CBLLog.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9388CBE721BF7195005CA66D /* CBLLog.mm */; }; @@ -1776,6 +1792,46 @@ AE83D0852C0637ED0055D2CF /* CBLIndexUpdater.mm in Sources */ = {isa = PBXBuildFile; fileRef = AE83D0832C0637ED0055D2CF /* CBLIndexUpdater.mm */; }; AE83D0862C0637ED0055D2CF /* CBLIndexUpdater.h in Headers */ = {isa = PBXBuildFile; fileRef = AE83D0822C0637ED0055D2CF /* CBLIndexUpdater.h */; settings = {ATTRIBUTES = (Private, ); }; }; AE83D0872C0637ED0055D2CF /* CBLIndexUpdater.mm in Sources */ = {isa = PBXBuildFile; fileRef = AE83D0832C0637ED0055D2CF /* CBLIndexUpdater.mm */; }; + AEA74F242CFE030E005F4810 /* CBLConsoleLogSink.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEA74F232CFE030E005F4810 /* CBLConsoleLogSink.mm */; }; + AEA74F252CFE030E005F4810 /* CBLConsoleLogSink.h in Headers */ = {isa = PBXBuildFile; fileRef = AEA74F222CFE030E005F4810 /* CBLConsoleLogSink.h */; settings = {ATTRIBUTES = (Public, ); }; }; + AEA74F262CFE030E005F4810 /* CBLConsoleLogSink.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEA74F232CFE030E005F4810 /* CBLConsoleLogSink.mm */; }; + AEA74F272CFE030E005F4810 /* CBLConsoleLogSink.h in Headers */ = {isa = PBXBuildFile; fileRef = AEA74F222CFE030E005F4810 /* CBLConsoleLogSink.h */; settings = {ATTRIBUTES = (Private, ); }; }; + AEA74F282CFE030E005F4810 /* CBLConsoleLogSink.h in Headers */ = {isa = PBXBuildFile; fileRef = AEA74F222CFE030E005F4810 /* CBLConsoleLogSink.h */; settings = {ATTRIBUTES = (Private, ); }; }; + AEA74F292CFE030E005F4810 /* CBLConsoleLogSink.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEA74F232CFE030E005F4810 /* CBLConsoleLogSink.mm */; }; + AEA74F2A2CFE030E005F4810 /* CBLConsoleLogSink.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEA74F232CFE030E005F4810 /* CBLConsoleLogSink.mm */; }; + AEA74F2B2CFE030E005F4810 /* CBLConsoleLogSink.h in Headers */ = {isa = PBXBuildFile; fileRef = AEA74F222CFE030E005F4810 /* CBLConsoleLogSink.h */; settings = {ATTRIBUTES = (Public, ); }; }; + AEA74F2E2CFE0581005F4810 /* CBLFileLogSink.h in Headers */ = {isa = PBXBuildFile; fileRef = AEA74F2C2CFE0581005F4810 /* CBLFileLogSink.h */; settings = {ATTRIBUTES = (Private, ); }; }; + AEA74F2F2CFE0581005F4810 /* CBLFileLogSink.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEA74F2D2CFE0581005F4810 /* CBLFileLogSink.mm */; }; + AEA74F302CFE0581005F4810 /* CBLFileLogSink.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEA74F2D2CFE0581005F4810 /* CBLFileLogSink.mm */; }; + AEA74F312CFE0581005F4810 /* CBLFileLogSink.h in Headers */ = {isa = PBXBuildFile; fileRef = AEA74F2C2CFE0581005F4810 /* CBLFileLogSink.h */; settings = {ATTRIBUTES = (Private, ); }; }; + AEA74F322CFE0581005F4810 /* CBLFileLogSink.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEA74F2D2CFE0581005F4810 /* CBLFileLogSink.mm */; }; + AEA74F332CFE0581005F4810 /* CBLFileLogSink.h in Headers */ = {isa = PBXBuildFile; fileRef = AEA74F2C2CFE0581005F4810 /* CBLFileLogSink.h */; settings = {ATTRIBUTES = (Public, ); }; }; + AEA74F342CFE0581005F4810 /* CBLFileLogSink.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEA74F2D2CFE0581005F4810 /* CBLFileLogSink.mm */; }; + AEA74F352CFE0581005F4810 /* CBLFileLogSink.h in Headers */ = {isa = PBXBuildFile; fileRef = AEA74F2C2CFE0581005F4810 /* CBLFileLogSink.h */; settings = {ATTRIBUTES = (Public, ); }; }; + AEA74F382CFE0B23005F4810 /* CBLLogSinks.h in Headers */ = {isa = PBXBuildFile; fileRef = AEA74F362CFE0B23005F4810 /* CBLLogSinks.h */; settings = {ATTRIBUTES = (Private, ); }; }; + AEA74F392CFE0B23005F4810 /* CBLLogSinks.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEA74F372CFE0B23005F4810 /* CBLLogSinks.mm */; }; + AEA74F3A2CFE0B23005F4810 /* CBLLogSinks.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEA74F372CFE0B23005F4810 /* CBLLogSinks.mm */; }; + AEA74F3B2CFE0B23005F4810 /* CBLLogSinks.h in Headers */ = {isa = PBXBuildFile; fileRef = AEA74F362CFE0B23005F4810 /* CBLLogSinks.h */; settings = {ATTRIBUTES = (Public, ); }; }; + AEA74F3C2CFE0B23005F4810 /* CBLLogSinks.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEA74F372CFE0B23005F4810 /* CBLLogSinks.mm */; }; + AEA74F3D2CFE0B23005F4810 /* CBLLogSinks.h in Headers */ = {isa = PBXBuildFile; fileRef = AEA74F362CFE0B23005F4810 /* CBLLogSinks.h */; settings = {ATTRIBUTES = (Private, ); }; }; + AEA74F3E2CFE0B23005F4810 /* CBLLogSinks.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEA74F372CFE0B23005F4810 /* CBLLogSinks.mm */; }; + AEA74F3F2CFE0B23005F4810 /* CBLLogSinks.h in Headers */ = {isa = PBXBuildFile; fileRef = AEA74F362CFE0B23005F4810 /* CBLLogSinks.h */; settings = {ATTRIBUTES = (Public, ); }; }; + AEA74F422CFE0BC3005F4810 /* CBLCustomLogSink.h in Headers */ = {isa = PBXBuildFile; fileRef = AEA74F402CFE0BC3005F4810 /* CBLCustomLogSink.h */; settings = {ATTRIBUTES = (Private, ); }; }; + AEA74F432CFE0BC3005F4810 /* CBLCustomLogSink.m in Sources */ = {isa = PBXBuildFile; fileRef = AEA74F412CFE0BC3005F4810 /* CBLCustomLogSink.m */; }; + AEA74F442CFE0BC3005F4810 /* CBLCustomLogSink.m in Sources */ = {isa = PBXBuildFile; fileRef = AEA74F412CFE0BC3005F4810 /* CBLCustomLogSink.m */; }; + AEA74F452CFE0BC3005F4810 /* CBLCustomLogSink.h in Headers */ = {isa = PBXBuildFile; fileRef = AEA74F402CFE0BC3005F4810 /* CBLCustomLogSink.h */; settings = {ATTRIBUTES = (Private, ); }; }; + AEA74F462CFE0BC3005F4810 /* CBLCustomLogSink.m in Sources */ = {isa = PBXBuildFile; fileRef = AEA74F412CFE0BC3005F4810 /* CBLCustomLogSink.m */; }; + AEA74F472CFE0BC3005F4810 /* CBLCustomLogSink.h in Headers */ = {isa = PBXBuildFile; fileRef = AEA74F402CFE0BC3005F4810 /* CBLCustomLogSink.h */; settings = {ATTRIBUTES = (Public, ); }; }; + AEA74F482CFE0BC3005F4810 /* CBLCustomLogSink.m in Sources */ = {isa = PBXBuildFile; fileRef = AEA74F412CFE0BC3005F4810 /* CBLCustomLogSink.m */; }; + AEA74F492CFE0BC3005F4810 /* CBLCustomLogSink.h in Headers */ = {isa = PBXBuildFile; fileRef = AEA74F402CFE0BC3005F4810 /* CBLCustomLogSink.h */; settings = {ATTRIBUTES = (Public, ); }; }; + AEAFDCEA2D10576400BA5C9C /* CBLLogSinks+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = AEAFDCE72D10576400BA5C9C /* CBLLogSinks+Internal.h */; }; + AEAFDCEB2D10576400BA5C9C /* CBLLogSinks+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = AEAFDCE72D10576400BA5C9C /* CBLLogSinks+Internal.h */; }; + AEAFDCED2D10576400BA5C9C /* CBLLogSinks+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = AEAFDCE72D10576400BA5C9C /* CBLLogSinks+Internal.h */; }; + AEAFDCF02D10576400BA5C9C /* CBLLogSinks+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = AEAFDCE72D10576400BA5C9C /* CBLLogSinks+Internal.h */; }; + AEAFDD972D15EC3B00BA5C9C /* LogTestOld.m in Sources */ = {isa = PBXBuildFile; fileRef = AEAFDD962D15EC3000BA5C9C /* LogTestOld.m */; }; + AEAFDD982D15EC3B00BA5C9C /* LogTestOld.m in Sources */ = {isa = PBXBuildFile; fileRef = AEAFDD962D15EC3000BA5C9C /* LogTestOld.m */; }; + AEAFDD992D15EC3B00BA5C9C /* LogTestOld.m in Sources */ = {isa = PBXBuildFile; fileRef = AEAFDD962D15EC3000BA5C9C /* LogTestOld.m */; }; + AEAFDD9A2D15EC3B00BA5C9C /* LogTestOld.m in Sources */ = {isa = PBXBuildFile; fileRef = AEAFDD962D15EC3000BA5C9C /* LogTestOld.m */; }; AEC806B72C89EA68001C9723 /* CBLArrayIndexConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = AEC806B52C89EA68001C9723 /* CBLArrayIndexConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; AEC806B82C89EA68001C9723 /* CBLArrayIndexConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = AEC806B62C89EA68001C9723 /* CBLArrayIndexConfiguration.m */; }; AEC806B92C89EA68001C9723 /* CBLArrayIndexConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = AEC806B62C89EA68001C9723 /* CBLArrayIndexConfiguration.m */; }; @@ -1790,6 +1846,14 @@ AECA89802CADADAA00C7B6BE /* UnnestArrayTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AECA897C2CADADAA00C7B6BE /* UnnestArrayTest.swift */; }; AECD5A162C0E21D900B1247E /* CBLIndexUpdater+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = AECD5A0F2C0E21D900B1247E /* CBLIndexUpdater+Internal.h */; }; AECD5A172C0E21D900B1247E /* CBLIndexUpdater+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = AECD5A0F2C0E21D900B1247E /* CBLIndexUpdater+Internal.h */; }; + AEFEED752D4A8D82008AF4C2 /* PartialIndexTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AEFEED742D4A8D6F008AF4C2 /* PartialIndexTest.m */; }; + AEFEED762D4A8D82008AF4C2 /* PartialIndexTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AEFEED742D4A8D6F008AF4C2 /* PartialIndexTest.m */; }; + AEFEED772D4A8D82008AF4C2 /* PartialIndexTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AEFEED742D4A8D6F008AF4C2 /* PartialIndexTest.m */; }; + AEFEED782D4A8D82008AF4C2 /* PartialIndexTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AEFEED742D4A8D6F008AF4C2 /* PartialIndexTest.m */; }; + AEFEED7A2D4AB999008AF4C2 /* PartialIndexTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEFEED792D4AB994008AF4C2 /* PartialIndexTest.swift */; }; + AEFEED7B2D4AB999008AF4C2 /* PartialIndexTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEFEED792D4AB994008AF4C2 /* PartialIndexTest.swift */; }; + AEFEED7C2D4AB999008AF4C2 /* PartialIndexTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEFEED792D4AB994008AF4C2 /* PartialIndexTest.swift */; }; + AEFEED7D2D4AB999008AF4C2 /* PartialIndexTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEFEED792D4AB994008AF4C2 /* PartialIndexTest.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -2195,8 +2259,6 @@ 1AA2EDF628A676E100DEB47E /* CBLConflictResolverBridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLConflictResolverBridge.h; sourceTree = ""; }; 1AA2EDF728A676E100DEB47E /* CBLConflictResolverBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CBLConflictResolverBridge.m; sourceTree = ""; }; 1AA3D6B022A1A3490098E16B /* ReplicatorTest+CustomConflict.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReplicatorTest+CustomConflict.swift"; sourceTree = ""; }; - 1AA3D77122AB06E10098E16B /* CustomLogger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CustomLogger.h; sourceTree = ""; }; - 1AA3D77222AB06E10098E16B /* CustomLogger.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CustomLogger.m; sourceTree = ""; }; 1AA6744E227925D20018CC6D /* QueryTest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QueryTest.h; sourceTree = ""; }; 1AA91DC522B0356000BF0BDE /* CustomLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomLogger.swift; sourceTree = ""; }; 1AAB2736227373EB0037A880 /* CBLConflictResolver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLConflictResolver.h; sourceTree = ""; }; @@ -2240,7 +2302,6 @@ 270AB2BB2073EF57009A4596 /* CBLChangeNotifier.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CBLChangeNotifier.m; sourceTree = ""; }; 2728509C1E99CA4D009CA22F /* CBLReplicator+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "CBLReplicator+Internal.h"; path = "../CBLReplicator+Internal.h"; sourceTree = ""; }; 27476651201912B5007B39D1 /* CBLErrors.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLErrors.h; sourceTree = ""; }; - 274D6C272011529D00DAB7DD /* CBLLog+Admin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CBLLog+Admin.h"; sourceTree = ""; }; 275229C51E776BC100E630FA /* CBLReplicator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CBLReplicator.h; sourceTree = ""; }; 275229C61E776BC100E630FA /* CBLReplicator.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CBLReplicator.mm; sourceTree = ""; }; 2753AFF11EC39CA200C12E98 /* CBLHTTPLogic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CBLHTTPLogic.h; sourceTree = ""; }; @@ -2293,6 +2354,7 @@ 40086B172B7EDDD400DA6770 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 40086B452B803B2A00DA6770 /* CBLBlockConflictResolver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CBLBlockConflictResolver.h; sourceTree = ""; }; 40086B522B803B2B00DA6770 /* CBLBlockConflictResolver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CBLBlockConflictResolver.m; sourceTree = ""; }; + 4009842A2D10F48E0029F26E /* CBLLogTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLLogTypes.h; sourceTree = ""; }; 400AAFCD2C2A843B00DB6223 /* CBLExtension.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CBLExtension.h; sourceTree = ""; }; 400AAFDA2C2A843B00DB6223 /* CBLExtension.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CBLExtension.mm; sourceTree = ""; }; 400AAFDF2C2A845E00DB6223 /* Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extension.swift; sourceTree = ""; }; @@ -2303,6 +2365,14 @@ 406F8DEA2C26901A000223FC /* QueryIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryIndex.swift; sourceTree = ""; }; 406F8DF92C27C302000223FC /* IndexUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndexUpdater.swift; sourceTree = ""; }; 406F8DFC2C27F097000223FC /* VectorSearchTest+Lazy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VectorSearchTest+Lazy.swift"; sourceTree = ""; }; + 409389E82D4AB81700691393 /* LogSinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogSinks.swift; sourceTree = ""; }; + 409389EB2D4AB8B500691393 /* ConsoleLogSink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleLogSink.swift; sourceTree = ""; }; + 409389EE2D4AB8EB00691393 /* CustomLogSink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomLogSink.swift; sourceTree = ""; }; + 409389F12D4AB99900691393 /* FileLogSink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileLogSink.swift; sourceTree = ""; }; + 40938A0B2D4AFA5900691393 /* LogSinkTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogSinkTest.swift; sourceTree = ""; }; + 40938A122D4B26D000691393 /* CBLLogSinks+Reset.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CBLLogSinks+Reset.h"; sourceTree = ""; }; + 40938A4F2D4B464500691393 /* CBLTestCustomLogSink.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLTestCustomLogSink.h; sourceTree = ""; }; + 40938A502D4B464500691393 /* CBLTestCustomLogSink.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CBLTestCustomLogSink.m; sourceTree = ""; }; 40A789282BE2C7D100CA43A1 /* CBL_EE.exp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.exports; path = CBL_EE.exp; sourceTree = ""; }; 40A789292BE2C7D100CA43A1 /* CBL.exp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.exports; path = CBL.exp; sourceTree = ""; }; 40A7892B2BE2C7D100CA43A1 /* CBL_EE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CBL_EE.txt; sourceTree = ""; }; @@ -2809,11 +2879,23 @@ AE5F25492CAC30DC00AAB7F4 /* UnnestArrayIndexTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UnnestArrayIndexTest.m; sourceTree = ""; }; AE83D0822C0637ED0055D2CF /* CBLIndexUpdater.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLIndexUpdater.h; sourceTree = ""; }; AE83D0832C0637ED0055D2CF /* CBLIndexUpdater.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CBLIndexUpdater.mm; sourceTree = ""; }; + AEA74F222CFE030E005F4810 /* CBLConsoleLogSink.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLConsoleLogSink.h; sourceTree = ""; }; + AEA74F232CFE030E005F4810 /* CBLConsoleLogSink.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CBLConsoleLogSink.mm; sourceTree = ""; }; + AEA74F2C2CFE0581005F4810 /* CBLFileLogSink.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLFileLogSink.h; sourceTree = ""; }; + AEA74F2D2CFE0581005F4810 /* CBLFileLogSink.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CBLFileLogSink.mm; sourceTree = ""; }; + AEA74F362CFE0B23005F4810 /* CBLLogSinks.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLLogSinks.h; sourceTree = ""; }; + AEA74F372CFE0B23005F4810 /* CBLLogSinks.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CBLLogSinks.mm; sourceTree = ""; }; + AEA74F402CFE0BC3005F4810 /* CBLCustomLogSink.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLCustomLogSink.h; sourceTree = ""; }; + AEA74F412CFE0BC3005F4810 /* CBLCustomLogSink.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CBLCustomLogSink.m; sourceTree = ""; }; + AEAFDCE72D10576400BA5C9C /* CBLLogSinks+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CBLLogSinks+Internal.h"; sourceTree = ""; }; + AEAFDD962D15EC3000BA5C9C /* LogTestOld.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LogTestOld.m; sourceTree = ""; }; AEC806B52C89EA68001C9723 /* CBLArrayIndexConfiguration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLArrayIndexConfiguration.h; sourceTree = ""; }; AEC806B62C89EA68001C9723 /* CBLArrayIndexConfiguration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CBLArrayIndexConfiguration.m; sourceTree = ""; }; AECA897C2CADADAA00C7B6BE /* UnnestArrayTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnnestArrayTest.swift; sourceTree = ""; }; AECD59F92C0E137200B1247E /* CBLQueryIndex+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CBLQueryIndex+Internal.h"; sourceTree = ""; }; AECD5A0F2C0E21D900B1247E /* CBLIndexUpdater+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CBLIndexUpdater+Internal.h"; sourceTree = ""; }; + AEFEED742D4A8D6F008AF4C2 /* PartialIndexTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PartialIndexTest.m; sourceTree = ""; }; + AEFEED792D4AB994008AF4C2 /* PartialIndexTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartialIndexTest.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -3043,6 +3125,7 @@ 938CDF071E807E98002EE790 /* Query */, 9338BEE21ED5475200634873 /* Replication */, 9388CC4521C250A4005CA66D /* Log */, + 409389E72D4AB7DE00691393 /* LogSink */, 27BE3B491E4E45CB0012B74A /* Tests */, 275F92761E4D30A4007FD5A2 /* CouchbaseLiteSwift.h */, 40E905462B5B6D9D00EDF483 /* CouchbaseLiteSwift.private.modulemap */, @@ -3082,6 +3165,7 @@ 93C50E9F21BDFB7C00C7E980 /* DocumentExpirationTest.swift */, 93F74A8A1EC5549C001289F8 /* FragmentTest.swift */, 9388CC5221C25C9E005CA66D /* LogTest.swift */, + 40938A0B2D4AFA5900691393 /* LogSinkTest.swift */, 93E17F141ED4ED4000671CA1 /* NotificationTest.swift */, 93A91C6B1E81CE4D003D01A2 /* QueryTest.swift */, 1A8F84E72893F187008C4333 /* QueryTest+Collection.swift */, @@ -3098,6 +3182,7 @@ AE5803A32B99C67D001A1BE3 /* VectorSearchTest.swift */, 406F8DFC2C27F097000223FC /* VectorSearchTest+Lazy.swift */, AECA897C2CADADAA00C7B6BE /* UnnestArrayTest.swift */, + AEFEED792D4AB994008AF4C2 /* PartialIndexTest.swift */, 1AA91DC522B0356000BF0BDE /* CustomLogger.swift */, 93249D81246B99FD000A8A6E /* iOS */, 939B79241E679017009A70EF /* Info.plist */, @@ -3145,6 +3230,17 @@ path = Extensions; sourceTree = ""; }; + 409389E72D4AB7DE00691393 /* LogSink */ = { + isa = PBXGroup; + children = ( + 409389E82D4AB81700691393 /* LogSinks.swift */, + 409389EB2D4AB8B500691393 /* ConsoleLogSink.swift */, + 409389EE2D4AB8EB00691393 /* CustomLogSink.swift */, + 409389F12D4AB99900691393 /* FileLogSink.swift */, + ); + name = LogSink; + sourceTree = ""; + }; 40A7892A2BE2C7D100CA43A1 /* Generated */ = { isa = PBXGroup; children = ( @@ -3515,6 +3611,8 @@ AE006DD32B7B9FEF00884E2B /* CBLWordEmbeddingModel.m */, 40086B452B803B2A00DA6770 /* CBLBlockConflictResolver.h */, 40086B522B803B2B00DA6770 /* CBLBlockConflictResolver.m */, + 40938A4F2D4B464500691393 /* CBLTestCustomLogSink.h */, + 40938A502D4B464500691393 /* CBLTestCustomLogSink.m */, ); path = Util; sourceTree = ""; @@ -3953,11 +4051,12 @@ 9388CC1021BF9E54005CA66D /* Log */ = { isa = PBXGroup; children = ( - 274D6C272011529D00DAB7DD /* CBLLog+Admin.h */, 93C50F0F21C068F300C7E980 /* CBLLog+Internal.h */, 934F4C9C1E241FB500F90659 /* CBLLog+Logging.h */, 9388CC5721C25FDE005CA66D /* CBLLog+Swift.h */, 932565BB21ED16BF0092F4E0 /* CBLLogFileConfiguration+Internal.h */, + AEAFDCE72D10576400BA5C9C /* CBLLogSinks+Internal.h */, + 40938A122D4B26D000691393 /* CBLLogSinks+Reset.h */, ); name = Log; sourceTree = ""; @@ -4083,6 +4182,7 @@ 93CD02271E9D8E9800AFB3FA /* Document */, 93CD02261E9D8E6200AFB3FA /* Query */, 938195D91EBCD5F70032CC51 /* Replicator */, + AEA74F212CFE02FC005F4810 /* LogSink */, 9388CBE521BF717C005CA66D /* Log */, 934F4C931E241FB500F90659 /* Internal */, 93BFCD931E0380A300E52F8A /* Tests */, @@ -4133,6 +4233,7 @@ 9388CBAA21BD916F005CA66D /* DocumentExpirationTest.m */, 931C14691EAAF08C0094F9B2 /* FragmentTest.m */, 9388CC3D21C1E2AC005CA66D /* LogTest.m */, + AEAFDD962D15EC3000BA5C9C /* LogTestOld.m */, 938B3701200D7D1D004485D8 /* MigrationTest.m */, 93384F8B1EB151C100976B41 /* MiscTest.m */, 1A4FE769225ED344009D5F43 /* MiscCppTest.mm */, @@ -4160,12 +4261,11 @@ 1A6F0941246C78FC0097D8B5 /* URLEndpointListenerTest.m */, 1A13DD3F28B881BF00BC1084 /* URLEndpointListenerTest+Main.m */, 1A9617F2289BF3C10037E78E /* URLEndpointListenerTest+Collection.m */, - 1AA3D77122AB06E10098E16B /* CustomLogger.h */, - 1AA3D77222AB06E10098E16B /* CustomLogger.m */, 40AA72A12C28B1F2007FB1E0 /* VectorSearchTest.h */, AE006DE62B7BB98B00884E2B /* VectorSearchTest.m */, 40AA72972C28B1A3007FB1E0 /* VectorSearchTest+Lazy.m */, AE5F25492CAC30DC00AAB7F4 /* UnnestArrayIndexTest.m */, + AEFEED742D4A8D6F008AF4C2 /* PartialIndexTest.m */, 93DECF3E200DBE5800F44953 /* Support */, 936483AA1E4431C6008D08B3 /* iOS */, 275FF5FA1E3FBD3B005F90DD /* Performance */, @@ -4367,6 +4467,22 @@ path = Util; sourceTree = ""; }; + AEA74F212CFE02FC005F4810 /* LogSink */ = { + isa = PBXGroup; + children = ( + AEA74F222CFE030E005F4810 /* CBLConsoleLogSink.h */, + AEA74F232CFE030E005F4810 /* CBLConsoleLogSink.mm */, + AEA74F402CFE0BC3005F4810 /* CBLCustomLogSink.h */, + AEA74F412CFE0BC3005F4810 /* CBLCustomLogSink.m */, + AEA74F2C2CFE0581005F4810 /* CBLFileLogSink.h */, + AEA74F2D2CFE0581005F4810 /* CBLFileLogSink.mm */, + AEA74F362CFE0B23005F4810 /* CBLLogSinks.h */, + AEA74F372CFE0B23005F4810 /* CBLLogSinks.mm */, + 4009842A2D10F48E0029F26E /* CBLLogTypes.h */, + ); + path = LogSink; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -4376,6 +4492,7 @@ files = ( 1AAB277A227793B20037A880 /* CBLConflictResolver.h in Headers */, 1AAFB6A1284A293700878453 /* CBLCollection+Swift.h in Headers */, + AEA74F312CFE0581005F4810 /* CBLFileLogSink.h in Headers */, 938B36A5200745FF004485D8 /* CBLQueryResultArray.h in Headers */, 935A58CF21AFAD31009A29CB /* CBLDocumentReplication+Internal.h in Headers */, 27F9619A1ED8D9440060F804 /* CBLReachability.h in Headers */, @@ -4412,6 +4529,7 @@ 93EC42DB1FB384D200D54BB4 /* CBLFunctionExpression.h in Headers */, 93B72074205CA67C0069F5FC /* CBLException.h in Headers */, 9308F40A1E64B23300F53EE4 /* Test.h in Headers */, + AEA74F452CFE0BC3005F4810 /* CBLCustomLogSink.h in Headers */, 1ABA63AC288135F4005835E7 /* CBLCollectionTypes.h in Headers */, 93EC42E71FB3930E00D54BB4 /* CBLQueryArrayExpression.h in Headers */, 9384D8411FC405D200FE89D8 /* CBLQueryFullTextFunction.h in Headers */, @@ -4424,6 +4542,7 @@ 9388CC3321C18672005CA66D /* CBLLog+Internal.h in Headers */, 9383A58B1F1EE8EF0083053D /* CBLQueryResult.h in Headers */, 27476665201946A3007B39D1 /* CBLErrors.h in Headers */, + AEA74F3D2CFE0B23005F4810 /* CBLLogSinks.h in Headers */, 933F83A421F9819B0093EC88 /* CBLDatabase+Swift.h in Headers */, 93B75C1B1E79EF690033B61B /* CBLQueryExpression.h in Headers */, 93B41CB11F04706100A7F114 /* CBLReplicatorChange.h in Headers */, @@ -4443,7 +4562,6 @@ 1AAFB66C284A260A00878453 /* CBLCollectionChange.h in Headers */, 9388CC0621BF750E005CA66D /* CBLFileLogger.h in Headers */, 9381960B1EC112010032CC51 /* CBLMutableDictionary.h in Headers */, - 9388CC3721C186DE005CA66D /* CBLLog+Admin.h in Headers */, 9381960D1EC112130032CC51 /* CBLArray.h in Headers */, 93CED8D420488DC400E6F0A4 /* CBLBlob+Swift.h in Headers */, 9381961F1EC11A8C0032CC51 /* CBLDictionary+Swift.h in Headers */, @@ -4469,10 +4587,12 @@ 93E17F0D1ED3BA6300671CA1 /* CBLDocumentChange.h in Headers */, 9388CC5921C25FDE005CA66D /* CBLLog+Swift.h in Headers */, 93DB7FED1ED8E1C000C4F845 /* CBLReplicatorConfiguration.h in Headers */, + AEAFDCF02D10576400BA5C9C /* CBLLogSinks+Internal.h in Headers */, 938196191EC113770032CC51 /* CBLArrayFragment.h in Headers */, 9384D8281FC405BF00FE89D8 /* CBLQueryFullTextExpression.h in Headers */, 275F92841E4D30A4007FD5A2 /* CouchbaseLiteSwift.h in Headers */, 9383A5851F1EE7C00083053D /* CBLQueryResultSet.h in Headers */, + 40938A152D4B26D000691393 /* CBLLogSinks+Reset.h in Headers */, 938196151EC1135F0032CC51 /* CBLDocumentFragment.h in Headers */, 93FD614A2020446300E7F6A1 /* CBLQueryBuilder.h in Headers */, 1A3471B226736E670042C6BA /* CBLQuery+N1QL.h in Headers */, @@ -4489,6 +4609,7 @@ 937A69041F0731230058277F /* CBLQueryFunction.h in Headers */, 9381960F1EC1121B0032CC51 /* CBLDictionary.h in Headers */, 275F92A31E4D377C007FD5A2 /* CBLBlob.h in Headers */, + AEA74F272CFE030E005F4810 /* CBLConsoleLogSink.h in Headers */, 1AEF059B283380F800D5DDEA /* CBLCollection.h in Headers */, 1ABA63B42881A9DD005835E7 /* CBLCollectionConfiguration+Internal.h in Headers */, 93B75C1E1E79EF7D0033B61B /* CBLQuery.h in Headers */, @@ -4509,6 +4630,7 @@ 272850A71E99CAA4009CA22F /* CBLReplicator+Internal.h in Headers */, 1AA2EDF828A676E100DEB47E /* CBLConflictResolverBridge.h in Headers */, 9381962B1EC15F410032CC51 /* CBLC4Document.h in Headers */, + 4009842E2D10F48E0029F26E /* CBLLogTypes.h in Headers */, 93B5036B1E64B093002C4680 /* CBLJSON.h in Headers */, 93B503641E64B07C002C4680 /* CBLCoreBridge.h in Headers */, 6932D4A829547B7E00D28C18 /* CBLQueryFullTextIndexExpressionProtocol.h in Headers */, @@ -4553,6 +4675,7 @@ 9343EF92207D611600F19A89 /* CBLQueryResultArray.h in Headers */, 9343EF93207D611600F19A89 /* CBLQueryArrayExpression.h in Headers */, 1A3471602671C9230042C6BA /* CBLValueIndexConfiguration.h in Headers */, + AEA74F2B2CFE030E005F4810 /* CBLConsoleLogSink.h in Headers */, 9343EF94207D611600F19A89 /* CBLQueryFullTextFunction.h in Headers */, 9343EF96207D611600F19A89 /* CBLQueryExpression.h in Headers */, 9343EF97207D611600F19A89 /* CBLQueryMeta.h in Headers */, @@ -4582,6 +4705,7 @@ 40FC1BD82B928A4F00394276 /* CBLIndexBuilder+Prediction.h in Headers */, 9343EFA9207D611600F19A89 /* CBLDatabase.h in Headers */, 9343EFAA207D611600F19A89 /* CBLQueryExpression+Internal.h in Headers */, + AEA74F352CFE0581005F4810 /* CBLFileLogSink.h in Headers */, 400AAFDB2C2A843B00DB6223 /* CBLExtension.h in Headers */, 9343EFAB207D611600F19A89 /* CBLAuthenticator+Internal.h in Headers */, 1AAFB671284A260A00878453 /* CBLCollectionChangeObservable.h in Headers */, @@ -4610,6 +4734,7 @@ 9343EFBD207D611600F19A89 /* CBLMutableArrayFragment.h in Headers */, 6932D4A729547B7B00D28C18 /* CBLQueryFullTextIndexExpressionProtocol.h in Headers */, 1A3470C3266F3E7C0042C6BA /* CBLIndexConfiguration.h in Headers */, + AEAFDCEB2D10576400BA5C9C /* CBLLogSinks+Internal.h in Headers */, 9343EFBE207D611600F19A89 /* CBLReplicator.h in Headers */, 40FC1C1C2B928B5000394276 /* CBLPredictiveIndex+Internal.h in Headers */, 9343EFBF207D611600F19A89 /* CBLFunctionExpression.h in Headers */, @@ -4686,6 +4811,7 @@ AEC806BB2C89EA68001C9723 /* CBLArrayIndexConfiguration.h in Headers */, 9343EFE0207D611600F19A89 /* CBLJSON.h in Headers */, 9388CBFD21BF74FD005CA66D /* CBLConsoleLogger.h in Headers */, + 40938A142D4B26D000691393 /* CBLLogSinks+Reset.h in Headers */, 9343EFE1207D611600F19A89 /* CBLDocumentChange.h in Headers */, 9343EFE2207D611600F19A89 /* CBLQueryResultSet.h in Headers */, 9343EFE3207D611600F19A89 /* CBLQueryFullTextExpression.h in Headers */, @@ -4700,6 +4826,7 @@ 933BFE1821A3BE960094530D /* CBLQuery+JSON.h in Headers */, 40FC1BDE2B928A4F00394276 /* CBLPrediction.h in Headers */, 9343EFE6207D611600F19A89 /* CBLReachability.h in Headers */, + AEA74F3B2CFE0B23005F4810 /* CBLLogSinks.h in Headers */, 40FC1BE62B928A4F00394276 /* CBLQueryFunction+Prediction.h in Headers */, 9343EFE7207D611600F19A89 /* CBLQueryBuilder.h in Headers */, 40FC1B602B9287BD00394276 /* CBLURLEndpointListenerConfiguration.h in Headers */, @@ -4726,7 +4853,7 @@ 40FC1C0B2B928ADC00394276 /* CBLListenerPasswordAuthenticator+Internal.h in Headers */, 9343EFF7207D611600F19A89 /* CBLQueryCollation.h in Headers */, 9343EFF8207D611600F19A89 /* CBLArrayFragment.h in Headers */, - 9388CC3821C186DF005CA66D /* CBLLog+Admin.h in Headers */, + AEA74F472CFE0BC3005F4810 /* CBLCustomLogSink.h in Headers */, 9343EFF9207D611600F19A89 /* CBLIndex.h in Headers */, 935A58D021AFAD31009A29CB /* CBLDocumentReplication+Internal.h in Headers */, 9343EFFB207D611600F19A89 /* CBLData.h in Headers */, @@ -4740,6 +4867,7 @@ 9343EFFF207D611600F19A89 /* CBLParameterExpression.h in Headers */, 9343F000207D611600F19A89 /* CBLCollationExpression.h in Headers */, 9343F001207D611600F19A89 /* MYErrorUtils.h in Headers */, + 4009842D2D10F48E0029F26E /* CBLLogTypes.h in Headers */, 9343F002207D611600F19A89 /* CBLQueryFunction.h in Headers */, 9343F003207D611600F19A89 /* CBLDictionary+Swift.h in Headers */, 9343F004207D611600F19A89 /* CBLQueryChange+Internal.h in Headers */, @@ -4760,6 +4888,7 @@ files = ( 1AAB2784227793DE0037A880 /* CBLConflict.h in Headers */, 9343F0B5207D61AB00F19A89 /* CBLQueryResultArray.h in Headers */, + 4009842C2D10F48E0029F26E /* CBLLogTypes.h in Headers */, 40FC1B4C2B92872000394276 /* CBLDatabase+Encryption.h in Headers */, 40FC1BF92B928A4F00394276 /* CBLVectorIndexConfiguration.h in Headers */, 93292D9F22BD448400A0862A /* CBLReplicatorConfiguration+Swift.h in Headers */, @@ -4775,6 +4904,7 @@ 9343F0BB207D61AB00F19A89 /* CBLBlobStream.h in Headers */, 9343F0BD207D61AB00F19A89 /* CBLLock.h in Headers */, 9343F0BE207D61AB00F19A89 /* MYBackgroundMonitor.h in Headers */, + 40938A162D4B26D000691393 /* CBLLogSinks+Reset.h in Headers */, 40FC1C142B928ADD00394276 /* CBLTLSIdentity+Swift.h in Headers */, 9343F0BF207D61AB00F19A89 /* CBLHTTPLogic.h in Headers */, 9343F0C0207D61AB00F19A89 /* CBLMisc.h in Headers */, @@ -4820,6 +4950,7 @@ 1AAB277C227793B70037A880 /* CBLConflictResolver.h in Headers */, 40FC1C122B928ADD00394276 /* CBLListenerPasswordAuthenticator+Internal.h in Headers */, 40FC1C322B928BB100394276 /* CBLMessageEndpoint+Internal.h in Headers */, + AEA74F382CFE0B23005F4810 /* CBLLogSinks.h in Headers */, 9343F0D9207D61AB00F19A89 /* CBLQueryResult.h in Headers */, 9343F0DA207D61AB00F19A89 /* CBLErrors.h in Headers */, 9343F0DB207D61AB00F19A89 /* CBLQueryExpression.h in Headers */, @@ -4832,6 +4963,7 @@ 9343F0DD207D61AB00F19A89 /* CBLArray+Swift.h in Headers */, 9343F0DE207D61AB00F19A89 /* CBLListenerToken.h in Headers */, 9343F0DF207D61AB00F19A89 /* CBLQueryOrdering.h in Headers */, + AEA74F2E2CFE0581005F4810 /* CBLFileLogSink.h in Headers */, 9343F0E0207D61AB00F19A89 /* CBLBasicAuthenticator.h in Headers */, 93EB264421DF1AE20006FB88 /* CBLDocumentFlags.h in Headers */, 40FC1B812B9288A800394276 /* CBLMessageEndpointListener.h in Headers */, @@ -4874,6 +5006,7 @@ 40FC1B722B92889800394276 /* CBLDatabaseEndpoint.h in Headers */, 9388CC0821BF750E005CA66D /* CBLFileLogger.h in Headers */, 40FC1B842B9288A800394276 /* CBLMessageEndpoint.h in Headers */, + AEAFDCEA2D10576400BA5C9C /* CBLLogSinks+Internal.h in Headers */, 40FC1B742B92889800394276 /* CBLReplicatorConfiguration+ServerCert.h in Headers */, 9343F0F1207D61AB00F19A89 /* CBLSessionAuthenticator.h in Headers */, 400AAFDC2C2A843B00DB6223 /* CBLExtension.h in Headers */, @@ -4898,6 +5031,7 @@ 9343F0FD207D61AB00F19A89 /* CBLQueryBuilder.h in Headers */, 40FC1BEE2B928A4F00394276 /* CBLQueryFunction+Vector.h in Headers */, 9343F0FF207D61AB00F19A89 /* CBLMutableArrayFragment.h in Headers */, + AEA74F282CFE030E005F4810 /* CBLConsoleLogSink.h in Headers */, 1AA2EE0328A682A800DEB47E /* CBLCollectionConfiguration+Swift.h in Headers */, 9343F100207D61AB00F19A89 /* CBLDatabaseChange.h in Headers */, 9343F101207D61AB00F19A89 /* CBLFragment.h in Headers */, @@ -4935,7 +5069,6 @@ 9343F116207D61AB00F19A89 /* CBLReplicator+Internal.h in Headers */, 9343F117207D61AB00F19A89 /* CBLC4Document.h in Headers */, 9343F118207D61AB00F19A89 /* CBLJSON.h in Headers */, - 9388CC3921C186DF005CA66D /* CBLLog+Admin.h in Headers */, 9343F119207D61AB00F19A89 /* CBLCoreBridge.h in Headers */, 935A58D121AFAD31009A29CB /* CBLDocumentReplication+Internal.h in Headers */, 1A3470EC266F69280042C6BA /* CBLIndexConfiguration+Internal.h in Headers */, @@ -4969,6 +5102,7 @@ 40FC1C042B928AB200394276 /* CBLKeyChain.h in Headers */, 40FC1BEC2B928A4F00394276 /* CBLDatabase+Prediction.h in Headers */, 1ABA63AE288135F8005835E7 /* CBLCollectionTypes.h in Headers */, + AEA74F422CFE0BC3005F4810 /* CBLCustomLogSink.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4978,6 +5112,7 @@ files = ( 2747666420191BFA007B39D1 /* CBLErrors.h in Headers */, 935A58CE21AFAD31009A29CB /* CBLDocumentReplication+Internal.h in Headers */, + AEA74F332CFE0581005F4810 /* CBLFileLogSink.h in Headers */, 938B36A4200745FF004485D8 /* CBLQueryResultArray.h in Headers */, 1A3BA96D272C589A002EAB2E /* CBLQueryObserver.h in Headers */, 93EC42E61FB3930E00D54BB4 /* CBLQueryArrayExpression.h in Headers */, @@ -5014,6 +5149,7 @@ 93EC42DF1FB386BE00D54BB4 /* CBLQueryExpression+Internal.h in Headers */, 937F01E61EFB280000060D64 /* CBLAuthenticator+Internal.h in Headers */, 9385F2661FC38F8900032037 /* CBLListenerToken.h in Headers */, + AEA74F492CFE0BC3005F4810 /* CBLCustomLogSink.h in Headers */, 1AC7EC29249DA24E00978C2E /* Foundation+CBL.h in Headers */, 93CD02721EA0004500AFB3FA /* CBLDocument.h in Headers */, 930B368E24AAFACB000DF2B3 /* CBLDocBranchIterator.h in Headers */, @@ -5026,6 +5162,7 @@ 93B41D641F0580E700A7F114 /* CBLQueryJoin.h in Headers */, 1ABA63B32881A9DC005835E7 /* CBLCollectionConfiguration+Internal.h in Headers */, 1A3471A626736E660042C6BA /* CBLQuery+N1QL.h in Headers */, + AEA74F3F2CFE0B23005F4810 /* CBLLogSinks.h in Headers */, 9388CC3221C18671005CA66D /* CBLLog+Internal.h in Headers */, 93DBD0112004BCE00017CA83 /* CBLURLEndpoint.h in Headers */, 1ABA63AB288135F3005835E7 /* CBLCollectionTypes.h in Headers */, @@ -5047,7 +5184,6 @@ 9388CC0521BF750E005CA66D /* CBLFileLogger.h in Headers */, 9398D9FF1E03531A00464432 /* CouchbaseLite.h in Headers */, 1A3470C1266F3E7C0042C6BA /* CBLIndexConfiguration.h in Headers */, - 9388CC3621C186DD005CA66D /* CBLLog+Admin.h in Headers */, 6932D48D2954640000D28C18 /* CBLQueryFullTextIndexExpression.h in Headers */, 937F01E11EFB269300060D64 /* CBLAuthenticator.h in Headers */, 934F4CB51E241FB500F90659 /* CBLPrefix.h in Headers */, @@ -5071,10 +5207,12 @@ 1AAFB6A0284A293700878453 /* CBLCollection+Swift.h in Headers */, 934A278C1F30E5A5003946A7 /* CBLAggregateExpression.h in Headers */, 93900CFE1EA197F000745D4F /* CBLDocument+Internal.h in Headers */, + AEAFDCED2D10576400BA5C9C /* CBLLogSinks+Internal.h in Headers */, 9322DCDF1F14603400C4ACF7 /* CBLQueryLimit.h in Headers */, 1AEF059A283380F800D5DDEA /* CBLCollection.h in Headers */, 69002EB9234E693F00776107 /* CBLErrorMessage.h in Headers */, 9388CC5821C25FDE005CA66D /* CBLLog+Swift.h in Headers */, + 40938A132D4B26D000691393 /* CBLLogSinks+Reset.h in Headers */, 934F4C5A1E1EF25E00F90659 /* Test.h in Headers */, 934A279F1F30E61B003946A7 /* CBLPropertyExpression.h in Headers */, 93CD02E61EA0382D00AFB3FA /* CBLMutableDictionary.h in Headers */, @@ -5091,6 +5229,7 @@ 934F4CAB1E241FB500F90659 /* CBLDatabase+Internal.h in Headers */, 931C145E1EAACAAA0094F9B2 /* CBLDictionaryFragment.h in Headers */, 27F961991ED8D9440060F804 /* CBLReachability.h in Headers */, + AEA74F252CFE030E005F4810 /* CBLConsoleLogSink.h in Headers */, 9388CBFB21BF74FD005CA66D /* CBLConsoleLogger.h in Headers */, 93FD61492020446300E7F6A1 /* CBLQueryBuilder.h in Headers */, 934F4CAF1E241FB500F90659 /* CBLLog+Logging.h in Headers */, @@ -5111,6 +5250,7 @@ 938E38811F3A5BB4006806C7 /* CBLQueryCollation.h in Headers */, 1AAFB66B284A260A00878453 /* CBLCollectionChange.h in Headers */, 931C14601EAACAD20094F9B2 /* CBLArrayFragment.h in Headers */, + 4009842B2D10F48E0029F26E /* CBLLogTypes.h in Headers */, 93FD618B2020757500E7F6A1 /* CBLIndex.h in Headers */, 930AE46D1EAA6C9100E92E9A /* CBLData.h in Headers */, 939B1B2C200990FB00FAA3CB /* CBLValueExpression.h in Headers */, @@ -5570,7 +5710,6 @@ }; 938CF9ED1E442A3E00291631 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = N2Q372V7W2; ProvisioningStyle = Automatic; }; 938CFA051E442A3E00291631 = { @@ -6032,6 +6171,7 @@ 937A693F1F1065610058277F /* CBLQueryMeta.m in Sources */, 9388CC4721C250D1005CA66D /* Log.swift in Sources */, 93248DE82005E72A00C15B00 /* Endpoint.swift in Sources */, + 409389ED2D4AB8B500691393 /* ConsoleLogSink.swift in Sources */, 9380D2641F0D7BD6007DD84A /* GroupBy.swift in Sources */, 27D7219C1F8E97F400AA4458 /* CBLFleece.mm in Sources */, 1A3F5556274345AA0088ECF1 /* Errors.swift in Sources */, @@ -6059,6 +6199,7 @@ 93CED8CF20488C4000E6F0A4 /* ListenerToken.swift in Sources */, 938196161EC113650032CC51 /* CBLDocumentFragment.m in Sources */, 9322DCE21F14603400C4ACF7 /* CBLQueryLimit.m in Sources */, + AEA74F3C2CFE0B23005F4810 /* CBLLogSinks.mm in Sources */, 9380D26A1F0D832C007DD84A /* Function.swift in Sources */, 9322DCAB1F0F0E0200C4ACF7 /* Ordering.swift in Sources */, 27E35A921E8C4F4300E103F9 /* Replicator.swift in Sources */, @@ -6066,8 +6207,10 @@ 938CDF251E807F86002EE790 /* FromRouter.swift in Sources */, 9384D8431FC405D200FE89D8 /* CBLQueryFullTextFunction.m in Sources */, 93E17F101ED3BA7800671CA1 /* CBLDocumentChange.m in Sources */, + 409389F22D4AB99900691393 /* FileLogSink.swift in Sources */, 93B503711E64B0A5002C4680 /* CBLParseDate.c in Sources */, 935A58BB21AFA34D009A29CB /* CBLDocumentReplication.mm in Sources */, + AEA74F302CFE0581005F4810 /* CBLFileLogSink.mm in Sources */, 1A1612B4283E29E600AA4987 /* CBLCollectionConfiguration.m in Sources */, 934A27A81F30E62F003946A7 /* CBLUnaryExpression.m in Sources */, 93EB261B21DDC34C0006FB88 /* IndexBuilder.swift in Sources */, @@ -6075,6 +6218,7 @@ 93B41CB31F04706100A7F114 /* CBLReplicatorChange.m in Sources */, 276740BA1EE7381E0036DE42 /* CBLTrustCheck.mm in Sources */, 933F45F61EC2A62000863ECB /* DocumentFragment.swift in Sources */, + 409389EF2D4AB8EB00691393 /* CustomLogSink.swift in Sources */, 93140F031F22AA68006E18EF /* Result.swift in Sources */, 1AAFB67F284A266F00878453 /* CollectionConfiguration.swift in Sources */, 93CED8C920488BC900E6F0A4 /* DatabaseChange.swift in Sources */, @@ -6115,6 +6259,7 @@ 27F9619C1ED8D9440060F804 /* CBLReachability.m in Sources */, 9384D84E1FC4163100FE89D8 /* FullTextExpression.swift in Sources */, 1A2AB75622BBFDB6000B9325 /* CBLConflictResolver.m in Sources */, + AEA74F442CFE0BC3005F4810 /* CBLCustomLogSink.m in Sources */, 9381962E1EC15F4A0032CC51 /* CBLData.mm in Sources */, 1A416032227D0AD40061A567 /* ConflictResolver.swift in Sources */, 934A27C21F31073E003946A7 /* ArrayExpressionIn.swift in Sources */, @@ -6174,7 +6319,9 @@ 937F01DE1EFB1A2900060D64 /* CBLSessionAuthenticator.m in Sources */, 939B1B5D2009C04100FAA3CB /* CBLQueryVariableExpression.m in Sources */, 275F928C1E4D3119007FD5A2 /* Database.swift in Sources */, + 409389EA2D4AB81700691393 /* LogSinks.swift in Sources */, 938CDF1A1E807F14002EE790 /* From.swift in Sources */, + AEA74F262CFE030E005F4810 /* CBLConsoleLogSink.mm in Sources */, 9380D2661F0D7BF7007DD84A /* GroupByRouter.swift in Sources */, 93AF51C61E80A70500E200F0 /* CBLQueryDataSource.m in Sources */, 27D721BD1F904B2500AA4458 /* CBLNewDictionary.mm in Sources */, @@ -6216,6 +6363,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + AEFEED7B2D4AB999008AF4C2 /* PartialIndexTest.swift in Sources */, 1AC7546F2897ADA0006CF48F /* ReplicatorTest+Collection.swift in Sources */, 27BE3B4F1E4E65EB0012B74A /* DocumentTest.swift in Sources */, 93629D011EC96DE700F79834 /* ArrayTest.swift in Sources */, @@ -6233,6 +6381,7 @@ 1A8DD7DA21C9876E00741C47 /* DateTimeQueryFunctionTest.swift in Sources */, 93A91C6C1E81CE4D003D01A2 /* QueryTest.swift in Sources */, 93C3985F1EC66A89005A7A96 /* DictionaryTest.swift in Sources */, + 40938A0F2D4AFA5900691393 /* LogSinkTest.swift in Sources */, 93C50EA021BDFB7C00C7E980 /* DocumentExpirationTest.swift in Sources */, 27BE3B4B1E4E46120012B74A /* CBLTestCase.swift in Sources */, 1AA91DC622B0356000BF0BDE /* CustomLogger.swift in Sources */, @@ -6276,6 +6425,7 @@ 1AC754702897ADA2006CF48F /* ReplicatorTest+Collection.swift in Sources */, 93BB1CA9246BB2D4004FFA00 /* DocumentExpirationTest.swift in Sources */, 93BB1CA7246BB2D4004FFA00 /* DictionaryTest.swift in Sources */, + 40938A0C2D4AFA5900691393 /* LogSinkTest.swift in Sources */, 93BB1CAA246BB2D4004FFA00 /* FragmentTest.swift in Sources */, 1A93FB0924F737320015D54D /* ReplicatorTest+PendingDocIds.swift in Sources */, 1A8F850A2893F32F008C4333 /* QueryTest+Collection.swift in Sources */, @@ -6291,6 +6441,7 @@ AE5803A52B99C67D001A1BE3 /* VectorSearchTest.swift in Sources */, AE5803D92B9B5B2A001A1BE3 /* WordEmbeddingModel.swift in Sources */, 93BB1C9E246BB2BF004FFA00 /* DatabaseTest.swift in Sources */, + AEFEED7D2D4AB999008AF4C2 /* PartialIndexTest.swift in Sources */, 93BB1C9C246BB2BB004FFA00 /* CBLTestCase.swift in Sources */, 93BB1CB8246BB2F4004FFA00 /* ReplicatorTest+CustomConflict.swift in Sources */, 406F8E002C27F0AB000223FC /* VectorSearchTest+Lazy.swift in Sources */, @@ -6316,6 +6467,7 @@ 93EB25C521CDCEC20006FB88 /* CBLQueryParameters.mm in Sources */, 9388CC0121BF74FD005CA66D /* CBLConsoleLogger.m in Sources */, 9343EF34207D611600F19A89 /* CBLBasicAuthenticator.m in Sources */, + AEA74F342CFE0581005F4810 /* CBLFileLogSink.mm in Sources */, 9343EF35207D611600F19A89 /* CBLStatus.mm in Sources */, 9343EF37207D611600F19A89 /* CBLMisc.m in Sources */, 9343EF38207D611600F19A89 /* CBLData.mm in Sources */, @@ -6326,6 +6478,7 @@ 9343EF3E207D611600F19A89 /* CBLAggregateExpression.m in Sources */, 9343EF3F207D611600F19A89 /* CBLJSON.mm in Sources */, 9343EF40207D611600F19A89 /* CBLParseDate.c in Sources */, + AEA74F2A2CFE030E005F4810 /* CBLConsoleLogSink.mm in Sources */, 9343EF41207D611600F19A89 /* CBLQueryResultSet.mm in Sources */, 9343EF42207D611600F19A89 /* CBLDatabaseChange.m in Sources */, 9343EF43207D611600F19A89 /* CBLQueryOrdering.m in Sources */, @@ -6367,6 +6520,7 @@ 40FC1B782B9288A800394276 /* CBLMessagingError.m in Sources */, 9343EF5C207D611600F19A89 /* CBLURLEndpoint.m in Sources */, 9343EF5D207D611600F19A89 /* CBLFleece.mm in Sources */, + AEA74F462CFE0BC3005F4810 /* CBLCustomLogSink.m in Sources */, 40FC1BFD2B928AB100394276 /* CBLCert.mm in Sources */, 9343EF5E207D611600F19A89 /* CBLVersion.m in Sources */, 40FC1B5D2B9287BD00394276 /* CBLTLSIdentity.mm in Sources */, @@ -6426,6 +6580,7 @@ 9343EF7F207D611600F19A89 /* CBLWebSocket.mm in Sources */, 9343EF80207D611600F19A89 /* CBLDocumentChangeNotifier.mm in Sources */, 1A34714F2671C8800042C6BA /* CBLFullTextIndexConfiguration.m in Sources */, + AEA74F3A2CFE0B23005F4810 /* CBLLogSinks.mm in Sources */, 9343EF82207D611600F19A89 /* CBLIndexBuilder.m in Sources */, 1A2AB75722BBFDB7000B9325 /* CBLConflictResolver.m in Sources */, 40FC1BE42B928A4F00394276 /* CBLPredictiveIndex.m in Sources */, @@ -6482,6 +6637,7 @@ 1AEF05A1283380F800D5DDEA /* CBLCollection.mm in Sources */, 4017E46C2BED6E5400A438EE /* CBLContextManager.m in Sources */, 9343F028207D61AB00F19A89 /* CBLIndexBuilder.m in Sources */, + 409389E92D4AB81700691393 /* LogSinks.swift in Sources */, 1A3471502671C8800042C6BA /* CBLFullTextIndexConfiguration.m in Sources */, 9388CC4E21C25141005CA66D /* ConsoleLogger.swift in Sources */, 40FC1C2B2B928B5000394276 /* CBLNoneVectorEncoding.mm in Sources */, @@ -6542,11 +6698,14 @@ 9343F049207D61AB00F19A89 /* CBLFragment.m in Sources */, 9343F04A207D61AB00F19A89 /* CBLQueryArrayExpression.m in Sources */, 9343F04B207D61AB00F19A89 /* Join.swift in Sources */, + AEA74F292CFE030E005F4810 /* CBLConsoleLogSink.mm in Sources */, 1A3470C8266F3E7C0042C6BA /* CBLIndexConfiguration.m in Sources */, 40FC1B4E2B92872900394276 /* CBLDatabase+Encryption.mm in Sources */, 9343F04C207D61AB00F19A89 /* OrderByRouter.swift in Sources */, 40FC1C6C2B928C1600394276 /* Prediction.swift in Sources */, 9343F04D207D61AB00F19A89 /* CBLParameterExpression.m in Sources */, + AEA74F432CFE0BC3005F4810 /* CBLCustomLogSink.m in Sources */, + 409389F32D4AB99900691393 /* FileLogSink.swift in Sources */, 9343F04E207D61AB00F19A89 /* HavingRouter.swift in Sources */, 40FC1C5D2B928C1600394276 /* ListenerAuthenticator.swift in Sources */, 9343F04F207D61AB00F19A89 /* CBLMutableFragment.m in Sources */, @@ -6603,8 +6762,11 @@ 9343F072207D61AB00F19A89 /* CBLCoreBridge.mm in Sources */, 9343F073207D61AB00F19A89 /* CBLAggregateExpression.m in Sources */, 40FC1BED2B928A4F00394276 /* CBLCoreMLPredictiveModel.m in Sources */, + 409389EC2D4AB8B500691393 /* ConsoleLogSink.swift in Sources */, + 409389F02D4AB8EB00691393 /* CustomLogSink.swift in Sources */, 9343F074207D61AB00F19A89 /* CBLDocument.mm in Sources */, 9343F075207D61AB00F19A89 /* DataConverter.swift in Sources */, + AEA74F392CFE0B23005F4810 /* CBLLogSinks.mm in Sources */, 40FC1BF42B928A4F00394276 /* CBLVectorEncoding.mm in Sources */, 9343F076207D61AB00F19A89 /* MutableDictionaryObject.swift in Sources */, 9343F077207D61AB00F19A89 /* DictionaryObject.swift in Sources */, @@ -6659,6 +6821,7 @@ 9343F096207D61AB00F19A89 /* GroupByRouter.swift in Sources */, 69002EC0234E695600776107 /* CBLErrorMessage.m in Sources */, 9343F097207D61AB00F19A89 /* CBLQueryDataSource.m in Sources */, + AEA74F2F2CFE0581005F4810 /* CBLFileLogSink.mm in Sources */, 9343F098207D61AB00F19A89 /* CBLNewDictionary.mm in Sources */, 1A53B2872966AB910010A73E /* FullTextIndexExpression.swift in Sources */, 9343F099207D61AB00F19A89 /* Select.swift in Sources */, @@ -6705,10 +6868,10 @@ files = ( 9343F139207D61EC00F19A89 /* NotificationTest.m in Sources */, 1A2F2C3727FD5B3300084B3C /* TrustCheckTest.m in Sources */, + 40938A512D4B464500691393 /* CBLTestCustomLogSink.m in Sources */, 930C7F9420FE4F7500C74A12 /* CBLMockConnection.m in Sources */, 9343F13A207D61EC00F19A89 /* MiscTest.m in Sources */, 933F841D220BA4100093EC88 /* PredictiveQueryTest+CoreML.m in Sources */, - 1AA3D78722AB07D70098E16B /* CustomLogger.m in Sources */, 9343F13B207D61EC00F19A89 /* CBLTestCase.m in Sources */, 1A4160DB22836E8C0061A567 /* ReplicatorTest+Main.m in Sources */, 930C7F9220FE4F7500C74A12 /* CBLMockConnectionErrorLogic.m in Sources */, @@ -6717,6 +6880,7 @@ 934EF8312460D0770053A47C /* TLSIdentityTest.m in Sources */, 9343F13C207D61EC00F19A89 /* DatabaseTest.m in Sources */, 1ABA63B028813A8C005835E7 /* ReplicatorTest+Collection.m in Sources */, + AEAFDD992D15EC3B00BA5C9C /* LogTestOld.m in Sources */, 40AA72962C28938E007FB1E0 /* VectorSearchTest.m in Sources */, 1AA6744A227924120018CC6D /* QueryTest+Meta.m in Sources */, AE006DE42B7BB22000884E2B /* CBLWordEmbeddingModel.m in Sources */, @@ -6743,6 +6907,7 @@ 9343F145207D61EC00F19A89 /* FragmentTest.m in Sources */, 9369A6AE207DD0FD009B5B83 /* DatabaseEncryptionTest.m in Sources */, 9343F146207D61EC00F19A89 /* QueryTest.m in Sources */, + AEFEED772D4A8D82008AF4C2 /* PartialIndexTest.m in Sources */, 1A4160D82283694B0061A567 /* ReplicatorTest+CustomConflict.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -6780,6 +6945,7 @@ 40AA729E2C28B1A3007FB1E0 /* VectorSearchTest+Lazy.m in Sources */, 9343F176207D633300F19A89 /* DocumentTest.m in Sources */, 1A13DD4228B882A800BC1084 /* URLEndpointListenerTest+Main.m in Sources */, + AEFEED782D4A8D82008AF4C2 /* PartialIndexTest.m in Sources */, 9388CBAE21BD9187005CA66D /* DocumentExpirationTest.m in Sources */, 9343F177207D633300F19A89 /* DictionaryTest.m in Sources */, 93EB25CD21CDD12A0006FB88 /* PredictiveQueryTest.m in Sources */, @@ -6789,12 +6955,13 @@ 1A621D7B2887DCFE0017F905 /* QueryTest+Collection.m in Sources */, AE5F255B2CAC311100AAB7F4 /* UnnestArrayIndexTest.m in Sources */, 933F841E220BA4100093EC88 /* PredictiveQueryTest+CoreML.m in Sources */, - 1AA3D78822AB07D80098E16B /* CustomLogger.m in Sources */, 1A2F2C3627FD5B2200084B3C /* TrustCheckTest.m in Sources */, 1A961801289BF7F90037E78E /* URLEndpointListenerTest+Collection.m in Sources */, AE006DE52B7BB22000884E2B /* CBLWordEmbeddingModel.m in Sources */, 1A93FAFC24F735250015D54D /* ReplicatorTest+PendingDocIds.m in Sources */, + AEAFDD9A2D15EC3B00BA5C9C /* LogTestOld.m in Sources */, 9343F17B207D633300F19A89 /* ArrayTest.m in Sources */, + 40938A522D4B464500691393 /* CBLTestCustomLogSink.m in Sources */, 1A6F0951246C792A0097D8B5 /* URLEndpointListenerTest.m in Sources */, 9388CC4121C1E2BA005CA66D /* LogTest.m in Sources */, 9343F17C207D633300F19A89 /* FragmentTest.m in Sources */, @@ -6824,6 +6991,7 @@ 9343F190207D636300F19A89 /* ReplicatorTest.swift in Sources */, 9343F191207D636300F19A89 /* NotificationTest.swift in Sources */, 9343F192207D636300F19A89 /* DatabaseTest.swift in Sources */, + 40938A0D2D4AFA5900691393 /* LogSinkTest.swift in Sources */, 9343F193207D636300F19A89 /* CBLTestHelper.m in Sources */, 1A8F850B2893F330008C4333 /* QueryTest+Collection.swift in Sources */, 93095B30246CF1F1005633B4 /* TLSIdentityTest.swift in Sources */, @@ -6839,6 +7007,7 @@ AE5803A42B99C67D001A1BE3 /* VectorSearchTest.swift in Sources */, AE5803D82B9B5B2A001A1BE3 /* WordEmbeddingModel.swift in Sources */, 9343F198207D636300F19A89 /* CBLTestCase.swift in Sources */, + AEFEED7C2D4AB999008AF4C2 /* PartialIndexTest.swift in Sources */, 93C50EB021BDFC7B00C7E980 /* DocumentExpirationTest.swift in Sources */, 9369A6B7207DEB60009B5B83 /* DatabaseEncryptionTest.swift in Sources */, 406F8DFF2C27F0A9000223FC /* VectorSearchTest+Lazy.swift in Sources */, @@ -6882,16 +7051,18 @@ 1AA6743A227924110018CC6D /* QueryTest+Join.m in Sources */, 93CD018C1E95546200AFB3FA /* NotificationTest.m in Sources */, 9388CC3F21C1E2B8005CA66D /* LogTest.m in Sources */, + 40938A542D4B464500691393 /* CBLTestCustomLogSink.m in Sources */, 938CFA2F1E442B5700291631 /* DatabaseTest.m in Sources */, - 1AA3D78622AB07D50098E16B /* CustomLogger.m in Sources */, 1A4160DD228375950061A567 /* ReplicatorTest+Main.m in Sources */, 1A93FB0824F735260015D54D /* ReplicatorTest+PendingDocIds.m in Sources */, 938CFA2D1E442B4200291631 /* DocumentTest.m in Sources */, + AEAFDD982D15EC3B00BA5C9C /* LogTestOld.m in Sources */, 40086B572B803B4300DA6770 /* CBLBlockConflictResolver.m in Sources */, 1A0BFA4727B5274300BA84E5 /* ReplicatorTest+SG.m in Sources */, 1A4160DF228375990061A567 /* ReplicatorTest+CustomConflict.m in Sources */, 93CD01901E95546200AFB3FA /* DictionaryTest.m in Sources */, 1ABA63AF28813A8A005835E7 /* ReplicatorTest+Collection.m in Sources */, + AEFEED762D4A8D82008AF4C2 /* PartialIndexTest.m in Sources */, 1A621D792887DCFC0017F905 /* QueryTest+Collection.m in Sources */, 9388CBAC21BD917D005CA66D /* DocumentExpirationTest.m in Sources */, AE5F25592CAC310800AAB7F4 /* UnnestArrayIndexTest.m in Sources */, @@ -6926,6 +7097,7 @@ 930AE46E1EAA6C9100E92E9A /* CBLData.mm in Sources */, 934F4C111E1EF19000F90659 /* CollectionUtils.m in Sources */, 934F4C5B1E1EF25E00F90659 /* Test.m in Sources */, + AEA74F482CFE0BC3005F4810 /* CBLCustomLogSink.m in Sources */, 72A879F01E2DD51C008466FF /* CBLBlob.mm in Sources */, 934A278E1F30E5A5003946A7 /* CBLAggregateExpression.m in Sources */, 934F4CAE1E241FB500F90659 /* CBLJSON.mm in Sources */, @@ -6933,6 +7105,7 @@ 9383A5861F1EE7C00083053D /* CBLQueryResultSet.mm in Sources */, 93E17F0C1ED3AC8100671CA1 /* CBLDatabaseChange.m in Sources */, 9388CC0921BF750E005CA66D /* CBLFileLogger.mm in Sources */, + AEA74F242CFE030E005F4810 /* CBLConsoleLogSink.mm in Sources */, AEC806B82C89EA68001C9723 /* CBLArrayIndexConfiguration.m in Sources */, 933208171E77415E000D9993 /* CBLQueryOrdering.m in Sources */, 931C14541EAABCE70094F9B2 /* CBLFragment.m in Sources */, @@ -6954,6 +7127,7 @@ 934A27AD1F30E641003946A7 /* CBLParameterExpression.m in Sources */, 9374A8A8201FC53600BA0D9E /* CBLReplicator+Backgrounding.m in Sources */, 275229C81E776BC100E630FA /* CBLReplicator.mm in Sources */, + AEA74F322CFE0581005F4810 /* CBLFileLogSink.mm in Sources */, 72A87A061E2E0E70008466FF /* CBLBlobStream.mm in Sources */, 932565A821ED13290092F4E0 /* CBLLogFileConfiguration.m in Sources */, 93E18735211122D9001D52B9 /* MYURLUtils.m in Sources */, @@ -6971,6 +7145,7 @@ 93EC42DC1FB384D200D54BB4 /* CBLFunctionExpression.m in Sources */, 93B41D661F0580E700A7F114 /* CBLQueryJoin.m in Sources */, 93CD024C1E9DA0AC00AFB3FA /* CBLC4Document.mm in Sources */, + AEA74F3E2CFE0B23005F4810 /* CBLLogSinks.mm in Sources */, 934A27A11F30E61B003946A7 /* CBLPropertyExpression.m in Sources */, 1AEF059E283380F800D5DDEA /* CBLCollection.mm in Sources */, 93B41CB21F04706100A7F114 /* CBLReplicatorChange.m in Sources */, @@ -7025,6 +7200,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 40938A532D4B464500691393 /* CBLTestCustomLogSink.m in Sources */, 72A87A0F1E32E858008466FF /* NotificationTest.m in Sources */, 9388CC3E21C1E2AD005CA66D /* LogTest.m in Sources */, 93384F8C1EB151C100976B41 /* MiscTest.m in Sources */, @@ -7033,13 +7209,13 @@ 1AF555C422946ED90077DF6D /* QueryTest+Main.m in Sources */, 9378C5971E25B473001BB196 /* CBLTestCase.m in Sources */, 1A4160C62283673E0061A567 /* ReplicatorTest+CustomConflict.m in Sources */, + AEAFDD972D15EC3B00BA5C9C /* LogTestOld.m in Sources */, 9378C5911E25B3F0001BB196 /* DatabaseTest.m in Sources */, 1A4FE76A225ED344009D5F43 /* MiscCppTest.mm in Sources */, 1A908401288027EE006B1885 /* ReplicatorTest+Collection.m in Sources */, AE5F25582CAC310700AAB7F4 /* UnnestArrayIndexTest.m in Sources */, 1AC83BC821C026D100792098 /* DateTimeQueryFunctionTest.m in Sources */, 938B3702200D7D1D004485D8 /* MigrationTest.m in Sources */, - 1AA3D78422AB07C50098E16B /* CustomLogger.m in Sources */, 9378C59E1E269F14001BB196 /* DocumentTest.m in Sources */, 1ADA05392240218F0068F745 /* AuthenticatorTest.m in Sources */, 9352945F1E51708E005CE4E8 /* DictionaryTest.m in Sources */, @@ -7054,6 +7230,7 @@ 93DD9BA81EB419BB00E502A2 /* ArrayTest.m in Sources */, 931C146A1EAAF08C0094F9B2 /* FragmentTest.m in Sources */, 40086B532B803B2B00DA6770 /* CBLBlockConflictResolver.m in Sources */, + AEFEED752D4A8D82008AF4C2 /* PartialIndexTest.m in Sources */, 9332082C1E774419000D9993 /* QueryTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -7067,6 +7244,7 @@ 93BB1CA2246BB2D3004FFA00 /* DictionaryTest.swift in Sources */, 93BB1CB5246BB2F3004FFA00 /* ReplicatorTest+CustomConflict.swift in Sources */, 93BB1CA5246BB2D3004FFA00 /* FragmentTest.swift in Sources */, + AEFEED7A2D4AB999008AF4C2 /* PartialIndexTest.swift in Sources */, 1A8F85082893F317008C4333 /* QueryTest+Collection.swift in Sources */, 93BB1CAE246BB2DC004FFA00 /* QueryTest.swift in Sources */, 93BB1CAD246BB2DC004FFA00 /* NotificationTest.swift in Sources */, @@ -7076,6 +7254,7 @@ 93BB1CB7246BB2F3004FFA00 /* CustomLogger.swift in Sources */, 1A93FB0A24F737330015D54D /* ReplicatorTest+PendingDocIds.swift in Sources */, 93BB1C99246BB2AB004FFA00 /* ArrayTest.swift in Sources */, + 40938A0E2D4AFA5900691393 /* LogSinkTest.swift in Sources */, 93BB1C9D246BB2BE004FFA00 /* DatabaseTest.swift in Sources */, 93BB1C9B246BB2BA004FFA00 /* CBLTestCase.swift in Sources */, 93BB1CA3246BB2D3004FFA00 /* DocumentTest.swift in Sources */, @@ -7613,6 +7792,8 @@ isa = XCBuildConfiguration; baseConfigurationReference = 9344A3611E44517B0091F581 /* CBL_ObjC_Tests_iOS_App.xcconfig */; buildSettings = { + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + DEVELOPMENT_TEAM = N2Q372V7W2; GCC_TREAT_WARNINGS_AS_ERRORS = NO; HEADER_SEARCH_PATHS = ( "${inherited}", @@ -7805,6 +7986,8 @@ isa = XCBuildConfiguration; baseConfigurationReference = 9344A3611E44517B0091F581 /* CBL_ObjC_Tests_iOS_App.xcconfig */; buildSettings = { + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + DEVELOPMENT_TEAM = N2Q372V7W2; GCC_TREAT_WARNINGS_AS_ERRORS = NO; HEADER_SEARCH_PATHS = ( "${inherited}", @@ -9169,6 +9352,8 @@ isa = XCBuildConfiguration; baseConfigurationReference = 9344A3611E44517B0091F581 /* CBL_ObjC_Tests_iOS_App.xcconfig */; buildSettings = { + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + DEVELOPMENT_TEAM = N2Q372V7W2; GCC_TREAT_WARNINGS_AS_ERRORS = NO; HEADER_SEARCH_PATHS = ( "${inherited}", @@ -9187,6 +9372,8 @@ isa = XCBuildConfiguration; baseConfigurationReference = 9344A3611E44517B0091F581 /* CBL_ObjC_Tests_iOS_App.xcconfig */; buildSettings = { + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + DEVELOPMENT_TEAM = N2Q372V7W2; GCC_TREAT_WARNINGS_AS_ERRORS = NO; HEADER_SEARCH_PATHS = ( "${inherited}", diff --git a/Objective-C/CBLConsoleLogger.m b/Objective-C/CBLConsoleLogger.m index eef775304..9be9e03eb 100644 --- a/Objective-C/CBLConsoleLogger.m +++ b/Objective-C/CBLConsoleLogger.m @@ -19,33 +19,58 @@ #import "CBLConsoleLogger.h" #import "CBLLog+Internal.h" -#import "CBLLog+Admin.h" +#import "CBLLogSinks+Internal.h" @implementation CBLConsoleLogger @synthesize level=_level, domains=_domains; -- (instancetype) initWithLogLevel: (CBLLogLevel)level { +- (instancetype) initWithDefault { self = [super init]; if (self) { - _level = level; + _level = kCBLLogLevelWarning; _domains = kCBLLogDomainAll; } return self; } - (void) setLevel: (CBLLogLevel)level { - _level = level; - [[CBLLog sharedInstance] synchronizeCallbackLogLevel]; + CBL_LOCK(self) { + _level = level; + [self updateConsoleLogSink]; + } +} + +- (CBLLogLevel) level { + CBL_LOCK(self) { + return _level; + } +} + +- (void) setDomains: (CBLLogDomain)domains { + CBL_LOCK(self) { + _domains = domains; + [self updateConsoleLogSink]; + } +} + +- (CBLLogDomain) domains { + CBL_LOCK(self) { + return _domains; + } +} + +- (void) updateConsoleLogSink { + CBLConsoleLogSink* sink = [[CBLConsoleLogSink alloc] initWithLevel: _level domains: _domains]; + sink.version = kCBLLogAPIOld; + CBLLogSinks.console = sink; } -- (void) logWithLevel: (CBLLogLevel)level domain: (CBLLogDomain)domain message: (NSString*)message { - if (self.level > level || (self.domains & domain) == 0) - return; - - NSString* levelName = CBLLog_GetLevelName(level); - NSString* domainName = CBLLog_GetDomainName(domain); - NSLog(@"CouchbaseLite %@ %@: %@", domainName, levelName, message); +- (void) logWithLevel: (CBLLogLevel)level + domain: (CBLLogDomain)domain + message: (nonnull NSString*)message +{ + // Do nothing: Logging is an internal functionality. } @end diff --git a/Objective-C/CBLDatabase.mm b/Objective-C/CBLDatabase.mm index a90ff9678..f034a3fae 100644 --- a/Objective-C/CBLDatabase.mm +++ b/Objective-C/CBLDatabase.mm @@ -30,8 +30,8 @@ #import "CBLIndexConfiguration+Internal.h" #import "CBLIndexSpec.h" #import "CBLIndex+Internal.h" -#import "CBLLog+Admin.h" #import "CBLLog+Internal.h" +#import "CBLLogSinks+Internal.h" #import "CBLMisc.h" #import "CBLQuery+Internal.h" #import "CBLQuery+N1QL.h" @@ -131,9 +131,8 @@ + (void) CBLInit { /** Check and show warning if file logging is not configured. */ + (void) checkFileLogging { - if (!CBLDatabase.log.file.config) { - CBLWarn(Database, @"Database.log.file.config is nil, meaning file logging is disabled. " - "Log files required for product support are not being generated."); + if (!CBLLogSinks.file) { + CBLWarn(Database, @"File logging is disabled. Log files required for product support are not being generated."); } } diff --git a/Objective-C/CBLDefaults.h b/Objective-C/CBLDefaults.h index d0c8f287e..aab91128c 100644 --- a/Objective-C/CBLDefaults.h +++ b/Objective-C/CBLDefaults.h @@ -50,6 +50,17 @@ extern const uint64_t kCBLDefaultLogFileMaxSize; /** [1] 1 rotated file present (2 total, including the currently active log file) */ extern const NSInteger kCBLDefaultLogFileMaxRotateCount; +#pragma mark - CBLFileLogSink + +/** [NO] Plaintext is not used, and instead binary encoding is used in log files */ +extern const BOOL kCBLDefaultFileLogSinkUsePlaintext; + +/** [524288] 512 KiB for the size of a log file */ +extern const long long kCBLDefaultFileLogSinkMaxSize; + +/** [2] 2 files preserved during each log rotation */ +extern const NSInteger kCBLDefaultFileLogSinkMaxKeptFiles; + #pragma mark - CBLFullTextIndexConfiguration /** [NO] Accents and ligatures are not ignored when indexing via full text search */ diff --git a/Objective-C/CBLDefaults.m b/Objective-C/CBLDefaults.m index 0509747e2..fb5f3979a 100644 --- a/Objective-C/CBLDefaults.m +++ b/Objective-C/CBLDefaults.m @@ -38,6 +38,14 @@ const NSInteger kCBLDefaultLogFileMaxRotateCount = 1; +#pragma mark - CBLFileLogSink + +const BOOL kCBLDefaultFileLogSinkUsePlaintext = NO; + +const long long kCBLDefaultFileLogSinkMaxSize = 524288; + +const NSInteger kCBLDefaultFileLogSinkMaxKeptFiles = 2; + #pragma mark - CBLFullTextIndexConfiguration const BOOL kCBLDefaultFullTextIndexIgnoreAccents = NO; diff --git a/Objective-C/CBLFileLogger.mm b/Objective-C/CBLFileLogger.mm index 980f7c7b3..211152d8c 100644 --- a/Objective-C/CBLFileLogger.mm +++ b/Objective-C/CBLFileLogger.mm @@ -17,13 +17,11 @@ // limitations under the License. // +#import "CBLDefaults.h" #import "CBLFileLogger.h" #import "CBLLog+Internal.h" #import "CBLLogFileConfiguration+Internal.h" -#import "CBLMisc.h" -#import "CBLStatus.h" -#import "CBLStringBytes.h" -#import "CBLVersion.h" +#import "CBLLogSinks+Internal.h" @implementation CBLFileLogger @@ -38,74 +36,67 @@ - (instancetype) initWithDefault { } - (void) setLevel: (CBLLogLevel)level { - if (_level != level) { - _level = level; - c4log_setBinaryFileLevel((C4LogLevel)level); + CBL_LOCK(self) { + if (_level != level) { + _level = level; + [self updateFileLogSink]; + } + } +} + +- (CBLLogLevel) level { + CBL_LOCK(self) { + return _level; } } - (void) setConfig: (CBLLogFileConfiguration*)config { - if (_config != config) { - if (config) { - // Copy and mark as READONLY - config = [[CBLLogFileConfiguration alloc] initWithConfig: config readonly: YES]; + CBL_LOCK(self) { + if (_config != config) { + if (config) { + // Copy and mark as READONLY + config = [[CBLLogFileConfiguration alloc] initWithConfig: config readonly: YES]; + } + _config = config; } - _config = config; - [self apply]; + [self updateFileLogSink]; } } -- (void) logWithLevel: (CBLLogLevel)level - domain: (CBLLogDomain)domain - message: (nonnull NSString*)message -{ - // Do nothing: Logging will be done in Lite Core +- (CBLLogFileConfiguration*) config { + CBL_LOCK(self) { + return _config; + } } -- (void) apply { - NSError* error; - - if (!_config) { - c4log_setBinaryFileLevel(kC4LogNone); - return; - } - - if (![self setupLogDirectory: _config.directory error: &error]) { - CBLWarnError(Database, @"Cannot setup log directory at %@: %@", _config.directory, error); - return; - } - - CBLStringBytes directory(_config.directory); - - C4LogFileOptions options = { - .log_level = (C4LogLevel)self.level, - .base_path = directory, - .max_size_bytes = (int64_t)_config.maxSize, - .max_rotate_count = (int32_t)_config.maxRotateCount, - .use_plaintext = (bool)_config.usePlainText, - .header = CBLStringBytes([CBLVersion userAgent]) - }; - - C4Error c4err; - if (!c4log_writeToBinaryFile(options, &c4err)) { - convertError(c4err, &error); - CBLWarnError(Database, @"Cannot enable file logging: %@", error); +- (void) updateFileLogSink { + if(_config) { + NSInteger maxRotateCount = (_config.maxRotateCount > 0) ? _config.maxRotateCount : kCBLDefaultLogFileMaxRotateCount; + CBLFileLogSink* sink = [[CBLFileLogSink alloc] initWithLevel: _level + directory: _config.directory + usePlaintext: _config.usePlainText + maxKeptFiles: maxRotateCount + 1 + maxFileSize: _config.maxSize + ]; + sink.version = kCBLLogAPIOld; + CBLLogSinks.file = sink; + } else { + CBLFileLogSink* sink = [[CBLFileLogSink alloc] initWithLevel: kCBLLogLevelNone + directory: @"" + usePlaintext: false + maxKeptFiles: 0 + maxFileSize: 0 + ]; + sink.version = kCBLLogAPIOld; + CBLLogSinks.file = sink; } } -- (BOOL) setupLogDirectory: (NSString*)directory error: (NSError**)outError { - NSError* error; - if (![[NSFileManager defaultManager] createDirectoryAtPath: directory - withIntermediateDirectories: YES - attributes: nil - error: &error]) { - if (!CBLIsFileExistsError(error)) { - if (outError) - *outError = error; - return NO; - } - } - return YES; +- (void) logWithLevel: (CBLLogLevel)level + domain: (CBLLogDomain)domain + message: (nonnull NSString*)message +{ + // Do nothing: Logging is an internal functionality. } @end diff --git a/Objective-C/CBLFullTextIndexConfiguration.h b/Objective-C/CBLFullTextIndexConfiguration.h index 34c1e0e84..9764d428f 100644 --- a/Objective-C/CBLFullTextIndexConfiguration.h +++ b/Objective-C/CBLFullTextIndexConfiguration.h @@ -2,7 +2,7 @@ // CBLFullTextIndexConfiguration.h // CouchbaseLite // -// Copyright (c) 2024 Couchbase, Inc All rights reserved. +// Copyright (c) 2025 Couchbase, Inc All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -27,6 +27,12 @@ NS_ASSUME_NONNULL_BEGIN */ @interface CBLFullTextIndexConfiguration : CBLIndexConfiguration +/** + A predicate expression defining conditions for indexing documents. + Only documents satisfying the predicate are included, enabling partial indexes. + */ +@property (nonatomic, readonly, nullable) NSString* where; + /** Set the true value to ignore accents/diacritical marks. */ @@ -41,11 +47,28 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly, nullable) NSString* language; /** - Constructor for creating a full-text index by using an array of expression strings + Initializes a full-text index by using an array of expression strings + @param expressions The array of expression strings. + @param ignoreAccents The flag to ignore accents/diacritical marks. + @param language Optional language code which is an ISO-639 language such as "en", "fr", etc. + @return The full-text index configuration object. + */ +- (instancetype) initWithExpression: (NSArray*)expressions + ignoreAccents: (BOOL)ignoreAccents + language: (nullable NSString*)language; + +/** + Initializes a full-text index with an array of expression strings and an optional where clause for a partial index. + @param where Optional where clause for partial indexing. + @param expressions The array of expression strings. + @param ignoreAccents The flag to ignore accents/diacritical marks. + @param language Optional language code which is an ISO-639 language such as "en", "fr", etc. + @return The full-text index configuration object. */ - (instancetype) initWithExpression: (NSArray*)expressions + where: (nullable NSString*)where ignoreAccents: (BOOL)ignoreAccents - language: (NSString* __nullable)language; + language: (nullable NSString*)language; @end diff --git a/Objective-C/CBLFullTextIndexConfiguration.m b/Objective-C/CBLFullTextIndexConfiguration.m index c91e6b98b..6e2854e62 100644 --- a/Objective-C/CBLFullTextIndexConfiguration.m +++ b/Objective-C/CBLFullTextIndexConfiguration.m @@ -2,7 +2,7 @@ // CBLFullTextIndexConfiguration.m // CouchbaseLite // -// Copyright (c) 2021 Couchbase, Inc All rights reserved. +// Copyright (c) 2025 Couchbase, Inc All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,14 +22,25 @@ @implementation CBLFullTextIndexConfiguration -@synthesize ignoreAccents=_ignoreAccents, language=_language; +@synthesize ignoreAccents=_ignoreAccents, language=_language, where=_where; - (instancetype) initWithExpression: (NSArray*)expressions ignoreAccents: (BOOL)ignoreAccents - language: (NSString* __nullable)language { + language: (nullable NSString*)language { + return [self initWithExpression: expressions + where: nil + ignoreAccents: ignoreAccents + language: language]; +} + +- (instancetype) initWithExpression: (NSArray*)expressions + where: (nullable NSString*)where + ignoreAccents: (BOOL)ignoreAccents + language: (nullable NSString*)language { self = [super initWithIndexType: kC4FullTextIndex expressions: expressions]; if (self) { + _where = where; // there is no default 'ignoreAccents', since its NOT an optional argument. _ignoreAccents = ignoreAccents; _language = language; @@ -39,12 +50,11 @@ - (instancetype) initWithExpression: (NSArray*)expressions - (C4IndexOptions) indexOptions { C4IndexOptions c4options = { }; - if (!_language) _language = [[NSLocale currentLocale] objectForKey: NSLocaleLanguageCode]; c4options.language = _language.UTF8String; - c4options.ignoreDiacritics = _ignoreAccents; + c4options.where = _where.UTF8String; return c4options; } diff --git a/Objective-C/CBLLog.h b/Objective-C/CBLLog.h index 26e389c3b..ce8ef9478 100644 --- a/Objective-C/CBLLog.h +++ b/Objective-C/CBLLog.h @@ -31,14 +31,17 @@ NS_ASSUME_NONNULL_BEGIN @interface CBLLog : NSObject /** Console logger writing log messages to the system console. */ -@property (readonly, nonatomic) CBLConsoleLogger* console; +@property (readonly, nonatomic) CBLConsoleLogger* console +__deprecated_msg("Use CBLLogSinks.console instead."); /** File logger writing log messages to files. */ -@property (readonly, nonatomic) CBLFileLogger* file; +@property (readonly, nonatomic) CBLFileLogger* file +__deprecated_msg("Use CBLLogSinks.file instead."); /** For setting a custom logger. Changing the log level of the assigned custom logger will require the custom logger to be reassigned so that the change can be affected. */ -@property (nonatomic, nullable) id custom; +@property (nonatomic, nullable) id custom +__deprecated_msg("Use CBLLogSinks.custom instead."); /** Not available */ - (instancetype) init NS_UNAVAILABLE; diff --git a/Objective-C/CBLLog.mm b/Objective-C/CBLLog.mm index c78c41b9b..75ac8439d 100644 --- a/Objective-C/CBLLog.mm +++ b/Objective-C/CBLLog.mm @@ -18,23 +18,10 @@ // #import "CBLLog.h" -#import "CBLLog+Admin.h" #import "CBLLog+Internal.h" #import "CBLLog+Logging.h" #import "CBLLog+Swift.h" -#import "CBLStringBytes.h" - -extern "C" { -#import "ExceptionUtils.h" -} - -C4LogDomain kCBL_LogDomainDatabase; -C4LogDomain kCBL_LogDomainQuery; -C4LogDomain kCBL_LogDomainSync; -C4LogDomain kCBL_LogDomainWebSocket; -C4LogDomain kCBL_LogDomainListener; - -static const char* kLevelNames[6] = {"Debug", "Verbose", "Info", "WARNING", "ERROR", "none"}; +#import "CBLLogSinks+Internal.h" // For bridging custom logger between Swift and Objective-C // without making CBLLogger protocol public @@ -42,100 +29,14 @@ @interface CBLCustomLogger : NSObject - (instancetype) initWithLevel: (CBLLogLevel)level logger: (CBLCustomLoggerBlock)logger; @end -@implementation CBLLog { - CBLLogLevel _callbackLogLevel; -} - -@synthesize console=_console, file=_file, custom=_custom; - -#ifdef DEBUG -static C4LogLevel string2level(NSString* value) { - if (value == nil) { - return kC4LogDebug; - } - - switch (value.length > 0 ? toupper([value characterAtIndex: 0]) : 'Y') { - case 'N': case 'F': case '0': - return kC4LogNone; - case 'V': case '2': - return kC4LogVerbose; - case 'D': case '3'...'9': - return kC4LogDebug; - default: - return kC4LogInfo; - } -} -#endif - -static C4LogDomain setNamedLogDomainLevel(const char *domainName, C4LogLevel level) { - C4LogDomain domain = c4log_getDomain(domainName, true); - if (domain) - c4log_setLevel(domain, level); - return domain; -} - -static NSDictionary* domainDictionary = nil; - -static CBLLogDomain toCBLLogDomain(C4LogDomain domain) { - if (!domainDictionary) { - domainDictionary = @{ @"DB": @(kCBLLogDomainDatabase), - @"Query": @(kCBLLogDomainQuery), - @"Sync": @(kCBLLogDomainReplicator), - @"SyncBusy": @(kCBLLogDomainReplicator), - @"Changes": @(kCBLLogDomainDatabase), - @"BLIP": @(kCBLLogDomainNetwork), - @"WS": @(kCBLLogDomainNetwork), - @"BLIPMessages": @(kCBLLogDomainNetwork), - @"Zip": @(kCBLLogDomainNetwork), - @"TLS": @(kCBLLogDomainNetwork), -#ifdef COUCHBASE_ENTERPRISE - @"Listener": @(kCBLLogDomainListener) -#endif - }; - } - - NSString* domainName = [NSString stringWithUTF8String: c4log_getDomainName(domain)]; - NSNumber* mapped = [domainDictionary objectForKey: domainName]; - return mapped ? mapped.integerValue : kCBLLogDomainDatabase; -} +// For bridging old custom logger and new custom log sink +@interface CBLCustomLogSinkBridge : NSObject +- (instancetype) initWithLogger: (id)logger; +@end -static void logCallback(C4LogDomain domain, C4LogLevel level, const char *fmt, va_list args) { - // Log message has been preformatted. - // c4log_writeToCallback() is called with preformatted=true: - NSString* message = [NSString stringWithUTF8String: fmt]; - - // Send to console and custom logger: - sendToCallbackLogger(domain, level, message); -} +@implementation CBLLog -static void sendToCallbackLogger(C4LogDomain d, C4LogLevel l, NSString* message) { - // CBLLog: - CBLLog* log = [CBLLog sharedInstance]; - - // Level: - CBLLogLevel level = (CBLLogLevel)l; - BOOL shouldLogToConsole = level >= log.console.level; - BOOL shouldLogToCustom = log.custom && level >= log.custom.level; - if (!shouldLogToConsole && !shouldLogToCustom) - return; - - // Domain: - CBLLogDomain domain = toCBLLogDomain(d); - - // Console log: - if (shouldLogToConsole) - [log.console logWithLevel: level domain: domain message: message]; - - // Custom log: - if (shouldLogToCustom) - [log.custom logWithLevel: level domain: domain message: message]; - -#ifdef DEBUG - // Breakpoint if enabled: - if (level >= kCBLLogLevelWarning && [NSUserDefaults.standardUserDefaults boolForKey: @"CBLBreakOnWarning"]) - MYBreakpoint(); // stops debugger at breakpoint. You can resume normally. -#endif -} +@synthesize console=_console, file=_file, custom=_custom; // Initialize the CBLLog object and register the logging callback. // It also sets up log domain levels based on user defaults named: @@ -149,59 +50,13 @@ static void sendToCallbackLogger(C4LogDomain d, C4LogLevel l, NSString* message) - (instancetype) initWithDefault { self = [super init]; if (self) { - // The most default callback log level: - C4LogLevel callbackLogLevel = kC4LogWarning; - -#ifdef DEBUG - // Check if user overrides the default callback log level: - NSString* userLogLevel = [NSUserDefaults.standardUserDefaults objectForKey: @"CBLLogLevel"]; - if (userLogLevel) { - callbackLogLevel = string2level(userLogLevel); - } - if (callbackLogLevel != kC4LogWarning) { - NSLog(@"CouchbaseLite minimum log level is %s", kLevelNames[callbackLogLevel]); - } -#endif - - // Enable callback logging: - c4log_writeToCallback(callbackLogLevel, &logCallback, true); - - // Set log level for each domains to the lowest: - kCBL_LogDomainDatabase = setNamedLogDomainLevel("DB", kC4LogDebug); - kCBL_LogDomainQuery = setNamedLogDomainLevel("Query", kC4LogDebug); - kCBL_LogDomainSync = setNamedLogDomainLevel("Sync", kC4LogDebug); - kCBL_LogDomainWebSocket = setNamedLogDomainLevel("WS", kC4LogDebug); - kCBL_LogDomainListener = setNamedLogDomainLevel("Listener", kC4LogDebug); - setNamedLogDomainLevel("BLIP", kC4LogDebug); - setNamedLogDomainLevel("SyncBusy", kC4LogDebug); - setNamedLogDomainLevel("TLS", kC4LogDebug); - setNamedLogDomainLevel("Changes", kC4LogDebug); - setNamedLogDomainLevel("Zip", kC4LogDebug); - setNamedLogDomainLevel("BLIPMessages", kC4LogDebug); - -#ifdef DEBUG - // Now map user defaults starting with CBLLog... to log levels: - NSDictionary* defaults = [NSUserDefaults.standardUserDefaults dictionaryRepresentation]; - for (NSString* key in defaults) { - if ([key hasPrefix: @"CBLLog"] && ![key isEqualToString: @"CBLLogLevel"]) { - const char *domainName = key.UTF8String + 6; - if (*domainName == 0) - domainName = "Default"; - C4LogDomain domain = c4log_getDomain(domainName, true); - C4LogLevel level = string2level(defaults[key]); - c4log_setLevel(domain, level); - NSLog(@"CouchbaseLite logging to %s domain at level %s", domainName, kLevelNames[level]); - } - } -#endif - - // Keep the current callback log level: - _callbackLogLevel = (CBLLogLevel)callbackLogLevel; + // Initialize new logging system + CBLAssertNotNil(CBLLogSinks.self); // Create console logger: - _console = [[CBLConsoleLogger alloc] initWithLogLevel: _callbackLogLevel]; + _console = [[CBLConsoleLogger alloc] initWithDefault]; - // Create file logger which will enable file logging immediately with default log rotation: + // Create file logger: _file = [[CBLFileLogger alloc] initWithDefault]; } return self; @@ -209,9 +64,17 @@ - (instancetype) initWithDefault { #pragma mark - Public +- (id) custom { + CBL_LOCK(self) { + return _custom; + } +} + - (void) setCustom: (id)custom { - _custom = custom; - [self synchronizeCallbackLogLevel]; + CBL_LOCK(self) { + _custom = custom; + [self updateCustomLogSink]; + } } #pragma mark - Internal @@ -225,16 +88,20 @@ + (instancetype) sharedInstance { return sharedInstance; } -- (void) synchronizeCallbackLogLevel { - // Synchronize log level between console and custom: - CBLLogLevel syncLogLevel = self.console.level; - if (self.custom && self.custom.level < syncLogLevel) - syncLogLevel = self.custom.level; +- (void) updateCustomLogSink { + CBLCustomLogSinkBridge* bridge = [[CBLCustomLogSinkBridge alloc] initWithLogger: _custom]; + CBLCustomLogSink* sink = [[CBLCustomLogSink alloc] initWithLevel: _custom.level logSink: bridge]; + sink.version = kCBLLogAPIOld; + CBLLogSinks.custom = sink; +} + +void cblLog(C4LogDomain domain, C4LogLevel level, NSString *msg, ...) { + va_list args; + va_start(args, msg); - if (syncLogLevel != _callbackLogLevel) { - c4log_setCallbackLevel((C4LogLevel)syncLogLevel); - _callbackLogLevel = syncLogLevel; - } + NSString *formatted = [[NSString alloc] initWithFormat: msg arguments: args]; + + [CBLLogSinks writeCBLLog: domain level: level message: formatted]; } #pragma mark - CBLLog+Swift @@ -266,51 +133,14 @@ - (void) logTo: (CBLLogDomain)domain level: (CBLLogLevel)level message: (NSStrin } - (void) setCustomLoggerWithLevel: (CBLLogLevel)level usingBlock: (CBLCustomLoggerBlock)logger { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" self.custom = [[CBLCustomLogger alloc] initWithLevel: level logger: logger]; +#pragma clang diagnostic pop } @end -void cblLog(C4LogDomain domain, C4LogLevel level, NSString *msg, ...) { - // Send preformatted message to litecore no-callback log: - va_list args; - va_start(args, msg); - NSString *nsmsg = [[NSString alloc] initWithFormat: msg arguments: args]; - CBLStringBytes c4msg(nsmsg); - c4slog(domain, level, c4msg); - - // Now log to console and custom logger: - sendToCallbackLogger(domain, level, nsmsg); -} - -NSString* CBLLog_GetLevelName(CBLLogLevel level) { - return [NSString stringWithUTF8String: kLevelNames[level]]; -} - -NSString* CBLLog_GetDomainName(CBLLogDomain domain) { - switch (domain) { - case kCBLLogDomainDatabase: - return @"Database"; - break; - case kCBLLogDomainQuery: - return @"Query"; - break; - case kCBLLogDomainReplicator: - return @"Replicator"; - break; - case kCBLLogDomainNetwork: - return @"Network"; - break; -#ifdef COUCHBASE_ENTERPRISE - case kCBLLogDomainListener: - return @"Listener"; - break; -#endif - default: - return @"Database"; - } -} - @implementation CBLCustomLogger { CBLLogLevel _level; CBLCustomLoggerBlock _logger; @@ -329,8 +159,26 @@ - (CBLLogLevel) level { return _level; } -- (void) logWithLevel:(CBLLogLevel)level domain:(CBLLogDomain)domain message:(NSString *)message { +- (void) logWithLevel:(CBLLogLevel)level domain: (CBLLogDomain)domain message: (NSString*)message { _logger(level, domain, message); } @end + +@implementation CBLCustomLogSinkBridge { + id _logger; +} + +- (instancetype) initWithLogger: (id)logger { + self = [super init]; + if (self) { + _logger = logger; + } + return self; +} + +- (void) writeLogWithLevel: (CBLLogLevel)level domain: (CBLLogDomain)domain message: (NSString*)message { + [_logger logWithLevel: level domain: domain message: message]; +} + +@end diff --git a/Objective-C/CBLLogger.h b/Objective-C/CBLLogger.h index 319f75031..006b9f825 100644 --- a/Objective-C/CBLLogger.h +++ b/Objective-C/CBLLogger.h @@ -17,33 +17,9 @@ // limitations under the License. // -NS_ASSUME_NONNULL_BEGIN - -/** - Log domain. - */ -typedef NS_OPTIONS(NSUInteger, CBLLogDomain) { - kCBLLogDomainAll = 1 << 0 | 1 << 1 | 1 << 2 | 1 << 3 | 1 << 4, ///< All domains - kCBLLogDomainDatabase = 1 << 0, ///< Database domain. - kCBLLogDomainQuery = 1 << 1, ///< Query domain. - kCBLLogDomainReplicator = 1 << 2, ///< Replicator domain. - kCBLLogDomainNetwork = 1 << 3, ///< Network domain. -#ifdef COUCHBASE_ENTERPRISE - kCBLLogDomainListener = 1 << 4 ///< Listener domain. -#endif -}; +#import -/** - Log level. - */ -typedef NS_ENUM(NSUInteger, CBLLogLevel) { - kCBLLogLevelDebug, ///< Debug log messages. Only present in debug builds of CouchbaseLite. - kCBLLogLevelVerbose, ///< Verbose log messages. - kCBLLogLevelInfo, ///< Informational log messages. - kCBLLogLevelWarning, ///< Warning log messages. - kCBLLogLevelError, ///< Error log messages. - kCBLLogLevelNone ///< Disabling log messages of a given log domain. -}; +NS_ASSUME_NONNULL_BEGIN /** Logger protocol diff --git a/Objective-C/CBLValueIndexConfiguration.h b/Objective-C/CBLValueIndexConfiguration.h index 3002c9acb..e53d577df 100644 --- a/Objective-C/CBLValueIndexConfiguration.h +++ b/Objective-C/CBLValueIndexConfiguration.h @@ -2,7 +2,7 @@ // CBLValueIndexConfiguration.h // CouchbaseLite // -// Copyright (c) 2024 Couchbase, Inc All rights reserved. +// Copyright (c) 2025 Couchbase, Inc All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -28,10 +28,27 @@ NS_ASSUME_NONNULL_BEGIN @interface CBLValueIndexConfiguration : CBLIndexConfiguration /** - Constructor for creating a value index by using an array of expression strings. + A predicate expression defining conditions for indexing documents. + Only documents satisfying the predicate are included, enabling partial indexes. + */ +@property (nonatomic, readonly, nullable) NSString* where; + +/** + Initializes a value index by using an array of expression strings. + @param expressions The array of expression strings. + @return The value index configuration object. */ - (instancetype) initWithExpression: (NSArray*)expressions; +/** + Initializes a value index with an array of expression strings and an optional where clause for a partial index. + @param expressions The array of expression strings. + @param where Optional where clause for partial indexing. + @return The value index configuration object. + */ +- (instancetype) initWithExpression: (NSArray*)expressions + where: (nullable NSString*)where; + @end NS_ASSUME_NONNULL_END diff --git a/Objective-C/CBLValueIndexConfiguration.m b/Objective-C/CBLValueIndexConfiguration.m index 78cdca6e2..288ec3252 100644 --- a/Objective-C/CBLValueIndexConfiguration.m +++ b/Objective-C/CBLValueIndexConfiguration.m @@ -2,7 +2,7 @@ // CBLValueIndexConfiguration.m // CouchbaseLite // -// Copyright (c) 2021 Couchbase, Inc All rights reserved. +// Copyright (c) 2025 Couchbase, Inc All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,8 +22,25 @@ @implementation CBLValueIndexConfiguration +@synthesize where=_where; + - (instancetype) initWithExpression: (NSArray*)expressions { - return [super initWithIndexType: kC4ValueIndex expressions: expressions]; + return [self initWithExpression: expressions where: nil]; +} + +- (instancetype) initWithExpression: (NSArray*)expressions + where: (nullable NSString*)where { + self = [super initWithIndexType: kC4ValueIndex expressions: expressions]; + if (self) { + _where = where; + } + return self; +} + +- (C4IndexOptions) indexOptions { + C4IndexOptions c4options = { }; + c4options.where = _where.UTF8String; + return c4options; } @end diff --git a/Objective-C/CouchbaseLite.h b/Objective-C/CouchbaseLite.h index 6f24fd78c..91f7a7078 100644 --- a/Objective-C/CouchbaseLite.h +++ b/Objective-C/CouchbaseLite.h @@ -38,6 +38,8 @@ FOUNDATION_EXPORT const unsigned char CouchbaseLiteVersionString[]; #import #import #import +#import +#import #import #import #import @@ -52,6 +54,7 @@ FOUNDATION_EXPORT const unsigned char CouchbaseLiteVersionString[]; #import #import #import +#import #import #import #import @@ -60,6 +63,8 @@ FOUNDATION_EXPORT const unsigned char CouchbaseLiteVersionString[]; #import #import #import +#import +#import #import #import #import diff --git a/Objective-C/Exports/CBL.txt b/Objective-C/Exports/CBL.txt index f4ab20ba0..f9008a3c4 100644 --- a/Objective-C/Exports/CBL.txt +++ b/Objective-C/Exports/CBL.txt @@ -30,6 +30,8 @@ .objc_class_name_CBLConflict .objc_class_name_CBLConflictResolver .objc_class_name_CBLConsoleLogger +.objc_class_name_CBLConsoleLogSink +.objc_class_name_CBLCustomLogSink .objc_class_name_CBLDatabase .objc_class_name_CBLDatabaseChange .objc_class_name_CBLDatabaseConfiguration @@ -39,6 +41,7 @@ .objc_class_name_CBLDocumentFragment .objc_class_name_CBLDocumentReplication .objc_class_name_CBLFileLogger +.objc_class_name_CBLFileLogSink .objc_class_name_CBLFragment .objc_class_name_CBLFullTextIndex .objc_class_name_CBLFullTextIndexConfiguration @@ -47,6 +50,7 @@ .objc_class_name_CBLIndexBuilder .objc_class_name_CBLIndexConfiguration .objc_class_name_CBLLog +.objc_class_name_CBLLogSinks .objc_class_name_CBLLogFileConfiguration .objc_class_name_CBLMutableArray .objc_class_name_CBLMutableDictionary @@ -95,11 +99,16 @@ _kCBLBlobType _kCBLDefaultDatabaseFullSync _kCBLDefaultDatabaseMmapEnabled _kCBLDefaultCollectionName +_kCBLDefaultFileLogSinkMaxKeptFiles +_kCBLDefaultFileLogSinkMaxSize +_kCBLDefaultFileLogSinkUsePlaintext _kCBLDefaultFullTextIndexIgnoreAccents _kCBLDefaultLogFileMaxRotateCount _kCBLDefaultLogFileMaxSize _kCBLDefaultLogFileUsePlaintext -_kCBLDefaultLogFileUsePlainText +_kCBLDefaultFileLogSinkUsePlaintext +_kCBLDefaultFileLogSinkMaxSize +_kCBLDefaultFileLogSinkMaxKeptFiles _kCBLDefaultReplicatorAcceptParentCookies _kCBLDefaultReplicatorAllowReplicatingInBackground _kCBLDefaultReplicatorContinuous diff --git a/Objective-C/Exports/Generated/CBL.exp b/Objective-C/Exports/Generated/CBL.exp index ac0e1da7d..9c802def8 100644 --- a/Objective-C/Exports/Generated/CBL.exp +++ b/Objective-C/Exports/Generated/CBL.exp @@ -10,7 +10,9 @@ .objc_class_name_CBLCollectionConfiguration .objc_class_name_CBLConflict .objc_class_name_CBLConflictResolver +.objc_class_name_CBLConsoleLogSink .objc_class_name_CBLConsoleLogger +.objc_class_name_CBLCustomLogSink .objc_class_name_CBLDatabase .objc_class_name_CBLDatabaseChange .objc_class_name_CBLDatabaseConfiguration @@ -19,6 +21,7 @@ .objc_class_name_CBLDocumentChange .objc_class_name_CBLDocumentFragment .objc_class_name_CBLDocumentReplication +.objc_class_name_CBLFileLogSink .objc_class_name_CBLFileLogger .objc_class_name_CBLFragment .objc_class_name_CBLFullTextIndex @@ -29,6 +32,7 @@ .objc_class_name_CBLIndexConfiguration .objc_class_name_CBLLog .objc_class_name_CBLLogFileConfiguration +.objc_class_name_CBLLogSinks .objc_class_name_CBLMutableArray .objc_class_name_CBLMutableDictionary .objc_class_name_CBLMutableDocument @@ -74,10 +78,15 @@ _kCBLBlobType _kCBLDefaultCollectionName _kCBLDefaultDatabaseFullSync _kCBLDefaultDatabaseMmapEnabled +_kCBLDefaultFileLogSinkMaxKeptFiles +_kCBLDefaultFileLogSinkMaxKeptFiles +_kCBLDefaultFileLogSinkMaxSize +_kCBLDefaultFileLogSinkMaxSize +_kCBLDefaultFileLogSinkUsePlaintext +_kCBLDefaultFileLogSinkUsePlaintext _kCBLDefaultFullTextIndexIgnoreAccents _kCBLDefaultLogFileMaxRotateCount _kCBLDefaultLogFileMaxSize -_kCBLDefaultLogFileUsePlainText _kCBLDefaultLogFileUsePlaintext _kCBLDefaultReplicatorAcceptParentCookies _kCBLDefaultReplicatorAllowReplicatingInBackground diff --git a/Objective-C/Exports/Generated/CBL_EE.exp b/Objective-C/Exports/Generated/CBL_EE.exp index 3c2e2ee30..a7169462b 100644 --- a/Objective-C/Exports/Generated/CBL_EE.exp +++ b/Objective-C/Exports/Generated/CBL_EE.exp @@ -11,8 +11,10 @@ .objc_class_name_CBLCollectionConfiguration .objc_class_name_CBLConflict .objc_class_name_CBLConflictResolver +.objc_class_name_CBLConsoleLogSink .objc_class_name_CBLConsoleLogger .objc_class_name_CBLCoreMLPredictiveModel +.objc_class_name_CBLCustomLogSink .objc_class_name_CBLDatabase .objc_class_name_CBLDatabaseChange .objc_class_name_CBLDatabaseConfiguration @@ -24,6 +26,7 @@ .objc_class_name_CBLDocumentReplication .objc_class_name_CBLEncryptionKey .objc_class_name_CBLExtension +.objc_class_name_CBLFileLogSink .objc_class_name_CBLFileLogger .objc_class_name_CBLFragment .objc_class_name_CBLFullTextIndex @@ -37,6 +40,7 @@ .objc_class_name_CBLListenerPasswordAuthenticator .objc_class_name_CBLLog .objc_class_name_CBLLogFileConfiguration +.objc_class_name_CBLLogSinks .objc_class_name_CBLMessage .objc_class_name_CBLMessageEndpoint .objc_class_name_CBLMessageEndpointListener @@ -112,6 +116,12 @@ _kCBLCertAttrURL _kCBLDefaultCollectionName _kCBLDefaultDatabaseFullSync _kCBLDefaultDatabaseMmapEnabled +_kCBLDefaultFileLogSinkMaxKeptFiles +_kCBLDefaultFileLogSinkMaxKeptFiles +_kCBLDefaultFileLogSinkMaxSize +_kCBLDefaultFileLogSinkMaxSize +_kCBLDefaultFileLogSinkUsePlaintext +_kCBLDefaultFileLogSinkUsePlaintext _kCBLDefaultFullTextIndexIgnoreAccents _kCBLDefaultListenerDisableTls _kCBLDefaultListenerEnableDeltaSync @@ -119,7 +129,6 @@ _kCBLDefaultListenerPort _kCBLDefaultListenerReadOnly _kCBLDefaultLogFileMaxRotateCount _kCBLDefaultLogFileMaxSize -_kCBLDefaultLogFileUsePlainText _kCBLDefaultLogFileUsePlaintext _kCBLDefaultReplicatorAcceptParentCookies _kCBLDefaultReplicatorAllowReplicatingInBackground diff --git a/Objective-C/Internal/CBLLog+Internal.h b/Objective-C/Internal/CBLLog+Internal.h index 616e5bcf9..0fb382829 100644 --- a/Objective-C/Internal/CBLLog+Internal.h +++ b/Objective-C/Internal/CBLLog+Internal.h @@ -27,13 +27,11 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype) sharedInstance; -- (void) synchronizeCallbackLogLevel; - @end @interface CBLConsoleLogger () -- (instancetype) initWithLogLevel: (CBLLogLevel)level; +- (instancetype) initWithDefault; @end diff --git a/Objective-C/Internal/CBLLogSinks+Internal.h b/Objective-C/Internal/CBLLogSinks+Internal.h new file mode 100644 index 000000000..7b9004f7d --- /dev/null +++ b/Objective-C/Internal/CBLLogSinks+Internal.h @@ -0,0 +1,60 @@ +// +// CBLLogSinks+Internal.h +// CouchbaseLite +// +// Copyright (c) 2024 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "CBLLogSinks.h" +#import "CBLConsoleLogSink.h" +#import "CBLCustomLogSink.h" +#import "CBLFileLogSink.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, CBLLogAPI) { + kCBLLogAPINone, + kCBLLogAPIOld, + kCBLLogAPINew, +}; + +@protocol CBLLogApiSource + +@property (nonatomic) CBLLogAPI version; + +@end + +@interface CBLLogSinks () + ++ (void) writeCBLLog: (C4LogDomain)domain level: (C4LogLevel)level message: (NSString*)message; + ++ (void) checkLogApiVersion: (nullable id) source; + +@end + +@interface CBLConsoleLogSink () + +@end + +@interface CBLCustomLogSink () + +@end + +@interface CBLFileLogSink () + ++ (void) setup: (nullable CBLFileLogSink*)logSink; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Objective-C/Internal/CBLLog+Admin.h b/Objective-C/Internal/CBLLogSinks+Reset.h similarity index 63% rename from Objective-C/Internal/CBLLog+Admin.h rename to Objective-C/Internal/CBLLogSinks+Reset.h index ffc0e0351..fdd09b600 100644 --- a/Objective-C/Internal/CBLLog+Admin.h +++ b/Objective-C/Internal/CBLLogSinks+Reset.h @@ -1,8 +1,8 @@ // -// CBLLog+Admin.h +// CBLLogSinks+Reset.h // CouchbaseLite // -// Copyright (c) 2024 Couchbase, Inc All rights reserved. +// Copyright (c) 2025 Couchbase, Inc All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,22 +15,11 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// -#import "CBLLogger.h" +#import "CBLLogSinks.h" -#ifdef __cplusplus -extern "C" { -#endif +@interface CBLLogSinks () -NS_ASSUME_NONNULL_BEGIN - -NSString* CBLLog_GetLevelName(CBLLogLevel level); - -NSString* CBLLog_GetDomainName(CBLLogDomain domain); - -NS_ASSUME_NONNULL_END ++ (void) resetApiVersion; -#ifdef __cplusplus -} -#endif +@end diff --git a/Objective-C/Internal/CBLStringBytes.h b/Objective-C/Internal/CBLStringBytes.h index b934607a0..7da292ff1 100644 --- a/Objective-C/Internal/CBLStringBytes.h +++ b/Objective-C/Internal/CBLStringBytes.h @@ -24,22 +24,30 @@ /** A slice holding the data of an NSString. If possible, it points the slice into the data of the NSString, requiring no copying. Otherwise it copies the characters into a small internal - buffer, or into a temporary heap block. - - NOTE: Since the slice may point directly into the NSString, if the string is mutable do not - mutate it while the stringBytes object is in scope! (Releasing the string is OK, as - stringBytes retains it.) */ + stack based buffer if the inline storage is enabled, or into a temporary heap block. + NOTE: + - Since the slice may point directly into the NSString, if the string is mutable do not + mutate it while the stringBytes object is in scope! (Releasing the string is OK, as + stringBytes retains it.) + - Disable the inline storage If the CBLStringBytes is used outside the scope that is created as + the inline storage (_local) is a stack base storage. + */ struct CBLStringBytes { - CBLStringBytes(NSString* =nil); + CBLStringBytes(NSString* str = nil, bool useLocalBuffer = true) + : _useLocalBuffer(useLocalBuffer) { + *this = str; + } void operator= (NSString*); - operator C4Slice() const {return bytes;} - operator fleece::slice() const {return bytes;} + operator C4Slice() const { return bytes; } + + operator fleece::slice() const { return bytes; } fleece::slice bytes; private: __strong id _storage {nullptr}; // keeps string alive, if `buf` points into it char _local[64]; + bool _useLocalBuffer; }; diff --git a/Objective-C/Internal/CBLStringBytes.mm b/Objective-C/Internal/CBLStringBytes.mm index 8ba577fd0..9f1c8d7c6 100644 --- a/Objective-C/Internal/CBLStringBytes.mm +++ b/Objective-C/Internal/CBLStringBytes.mm @@ -21,10 +21,6 @@ using namespace fleece; -CBLStringBytes::CBLStringBytes(__unsafe_unretained NSString* str) { - *this = str; -} - void CBLStringBytes::operator= (__unsafe_unretained NSString* str) { if (!str) { bytes = nullslice; @@ -42,7 +38,7 @@ NSUInteger byteCount; NSUInteger length = str.length; - if (length <= sizeof(_local)) { + if (_useLocalBuffer && length <= sizeof(_local)) { // Next try to copy the UTF-8 into a smallish stack-based buffer: NSRange remaining; BOOL ok = [str getBytes: _local maxLength: sizeof(_local) usedLength: &byteCount diff --git a/Objective-C/LogSink/CBLConsoleLogSink.h b/Objective-C/LogSink/CBLConsoleLogSink.h new file mode 100644 index 000000000..27bb9b8bb --- /dev/null +++ b/Objective-C/LogSink/CBLConsoleLogSink.h @@ -0,0 +1,54 @@ +// +// CBLConsoleLogSink.h +// CouchbaseLite +// +// Copyright (c) 2025 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** A log sink that writes log messages to the console. */ +@interface CBLConsoleLogSink : NSObject + +/** The minimum log level of the log messages to be logged. The default log level is warning. */ +@property (nonatomic, readonly) CBLLogLevel level; + +/** The set of log domains of the log messages to be logged. The default is all domains. */ +@property (nonatomic, readonly) CBLLogDomain domains; + +/** + Initializes a console log sink with a specified log level. + + @param level The minimum log level. + */ +- (instancetype) initWithLevel: (CBLLogLevel)level; + +/** + Initializes a console log sink with a specified log level and log domains. + + @param level The minimum log level. + @param domains The set of log domains. + */ +- (instancetype) initWithLevel: (CBLLogLevel)level domains: (CBLLogDomain)domains; + +/** Not available */ +- (instancetype) init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Objective-C/LogSink/CBLConsoleLogSink.mm b/Objective-C/LogSink/CBLConsoleLogSink.mm new file mode 100644 index 000000000..fb3b2ca2d --- /dev/null +++ b/Objective-C/LogSink/CBLConsoleLogSink.mm @@ -0,0 +1,77 @@ +// +// CBLConsoleLogSink.m +// CouchbaseLite +// +// Copyright (c) 2025 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "CBLConsoleLogSink.h" +#import "CBLLogSinks+Internal.h" + +static NSArray* logLevelNames = @[@"Debug", @"Verbose", @"Info", @"WARNING", @"ERROR", @"none"]; + +@implementation CBLConsoleLogSink + +@synthesize level=_level, domains=_domains, version=_version; + +- (instancetype) initWithLevel: (CBLLogLevel)level { + return [self initWithLevel: level domains: kCBLLogDomainAll]; +} + +- (instancetype) initWithLevel: (CBLLogLevel)level domains: (CBLLogDomain)domains { + self = [super init]; + if (self) { + _level = level; + _domains = domains; + _version = kCBLLogAPINew; + } + return self; +} + +- (void) writeLogWithLevel: (CBLLogLevel)level domain: (CBLLogDomain)domain message: (NSString*)message { + if (level < _level || (_domains & domain) == 0) { + return; + } + + NSString* levelName = logLevelNames[level]; + NSString* domainName = [self domainName: domain]; + NSLog(@"CouchbaseLite %@ %@: %@", domainName, levelName, message); +} + +- (NSString*) domainName: (CBLLogDomain)domain { + switch (domain) { + case kCBLLogDomainDatabase: + return @"Database"; + break; + case kCBLLogDomainQuery: + return @"Query"; + break; + case kCBLLogDomainReplicator: + return @"Replicator"; + break; + case kCBLLogDomainNetwork: + return @"Network"; + break; +#ifdef COUCHBASE_ENTERPRISE + case kCBLLogDomainListener: + return @"Listener"; + break; +#endif + default: + return @"Database"; + } +} + +@end diff --git a/Objective-C/LogSink/CBLCustomLogSink.h b/Objective-C/LogSink/CBLCustomLogSink.h new file mode 100644 index 000000000..48b54b78f --- /dev/null +++ b/Objective-C/LogSink/CBLCustomLogSink.h @@ -0,0 +1,67 @@ +// +// CBLCustomLogSink.h +// CouchbaseLite +// +// Copyright (c) 2025 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Protocol for custom log sinks to handle log messages. */ +@protocol CBLLogSinkProtocol + +/** Writes a log message with the given level, domain, and content. */ +- (void) writeLogWithLevel: (CBLLogLevel)level domain: (CBLLogDomain)domain message: (NSString*)message; + +@end + +/** A log sink that writes log messages to a custom log sink implementation. */ +@interface CBLCustomLogSink : NSObject + +/** The minimum log level of the log messages to be logged. The default log level is warning. */ +@property (nonatomic, readonly) CBLLogLevel level; + +/** The set of log domains of the log messages to be logged. The default is all domains. */ +@property (nonatomic, readonly) CBLLogDomain domains; + +/** The custom log sink implementation that receives the log messages. */ +@property (nonatomic, readonly) id logSink; + +/** + Initializes a custom log sink with a specified log level and custom log sink implementation. + + @param level The minimum log level. + @param logSink The custom log sink implementation. + */ +- (instancetype) initWithLevel: (CBLLogLevel)level + logSink: (id)logSink; + +/** + Initializes a custom log sink with a specified log level, log domains, and custom log sink implementation. + + @param level The minimum log level. + @param domains The set of log domains. + @param logSink The custom log sink implementation. + */ +- (instancetype) initWithLevel: (CBLLogLevel)level + domains: (CBLLogDomain)domains + logSink: (id)logSink; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Objective-C/LogSink/CBLCustomLogSink.m b/Objective-C/LogSink/CBLCustomLogSink.m new file mode 100644 index 000000000..b9860d1b0 --- /dev/null +++ b/Objective-C/LogSink/CBLCustomLogSink.m @@ -0,0 +1,51 @@ +// +// CBLCustomLogSink.m +// CouchbaseLite +// +// Copyright (c) 2025 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "CBLCustomLogSink.h" +#import "CBLLogSinks+Internal.h" + +@implementation CBLCustomLogSink + +@synthesize level = _level, domains=_domains, logSink = _logSink, version=_version; + +- (instancetype) initWithLevel: (CBLLogLevel)level logSink: (id)logSink { + return [self initWithLevel:level domains:kCBLLogDomainAll logSink: logSink]; +} + +- (instancetype) initWithLevel: (CBLLogLevel)level + domains: (CBLLogDomain)domains + logSink: (id)logSink { + self = [super init]; + if (self) { + _domains = domains; + _level = level; + _logSink = logSink; + _version = kCBLLogAPINew; + } + return self; +} + +- (void) writeLogWithLevel: (CBLLogLevel)level domain: (CBLLogDomain)domain message: (NSString*)message { + if (level < _level || (_domains & domain) == 0) { + return; + } + [self.logSink writeLogWithLevel: level domain: domain message: message]; +} + +@end diff --git a/Objective-C/LogSink/CBLFileLogSink.h b/Objective-C/LogSink/CBLFileLogSink.h new file mode 100644 index 000000000..3cebe8dc6 --- /dev/null +++ b/Objective-C/LogSink/CBLFileLogSink.h @@ -0,0 +1,72 @@ +// +// CBLFileLogSink.h +// CouchbaseLite +// +// Copyright (c) 2025 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** A log sink that writes log messages to files. */ +@interface CBLFileLogSink : NSObject + +/** The minimum log level for log messages. Default is `kCBLLogLevelNone`, meaning no logging. */ +@property (nonatomic, readonly) CBLLogLevel level; + +/** The directory where log files will be stored. */ +@property (nonatomic, copy, readonly) NSString* directory; + +/** To use plain text file format instead of the default binary format. */ +@property (nonatomic, readonly) BOOL usePlaintext; + +/** The maximum number of log files to keep. Default is `kCBLDefaultMaxKeptFiles`. */ +@property (nonatomic, readonly) NSInteger maxKeptFiles; + +/** The maximum size of a log file before rotation. Default is `kCBLDefaultMaxFileSize`. */ +@property (nonatomic, readonly) long long maxFileSize; + +/** + Initializes with log level and directory. + + @param level The minimum log level. + @param directory The log file directory. + */ +- (instancetype) initWithLevel: (CBLLogLevel) level + directory: (NSString*) directory; + +/** + Initializes with full configuration options. + + @param level The minimum log level. + @param directory The log file directory. + @param usePlainText Whether to use plain text format. + @param maxKeptFiles The maximum number of log files. + @param maxFileSize The maximum size of a log file. + */ +- (instancetype) initWithLevel: (CBLLogLevel)level + directory: (NSString*)directory + usePlaintext: (BOOL)usePlainText + maxKeptFiles: (NSInteger)maxKeptFiles + maxFileSize: (long long)maxFileSize; + +/** Not available */ +- (instancetype) init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Objective-C/LogSink/CBLFileLogSink.mm b/Objective-C/LogSink/CBLFileLogSink.mm new file mode 100644 index 000000000..2ba4c309c --- /dev/null +++ b/Objective-C/LogSink/CBLFileLogSink.mm @@ -0,0 +1,141 @@ +// +// CBLFileLogSink.mm +// CouchbaseLite +// +// Copyright (c) 2025 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "CBLDefaults.h" +#import "CBLFileLogSink.h" +#import "CBLLogSinks+Internal.h" +#import "CBLMisc.h" +#import "CBLStatus.h" +#import "CBLStringBytes.h" +#import "CBLVersion.h" + +@implementation CBLFileLogSink + +@synthesize level=_level, directory=_directory, usePlaintext=_usePlaintext; +@synthesize maxKeptFiles=_maxKeptFiles, maxFileSize=_maxFileSize, version=_version; + +- (instancetype) initWithLevel: (CBLLogLevel)level + directory: (NSString*)directory { + return [self initWithLevel: level + directory: directory + usePlaintext: kCBLDefaultFileLogSinkUsePlaintext + maxKeptFiles: kCBLDefaultFileLogSinkMaxKeptFiles + maxFileSize: kCBLDefaultFileLogSinkMaxSize]; +} + +- (instancetype) initWithLevel: (CBLLogLevel)level + directory: (NSString*)directory + usePlaintext: (BOOL)usePlaintext + maxKeptFiles: (NSInteger)maxKeptFiles + maxFileSize: (long long)maxFileSize +{ + self = [super init]; + if (self) { + CBLAssertNotNil(directory); + _level = level; + _directory = directory; + _usePlaintext = usePlaintext; + _maxKeptFiles = maxKeptFiles; + _maxFileSize = maxFileSize; + _version = kCBLLogAPINew; + } + return self; +} + ++ (void) setup: (CBLFileLogSink*)logSink { + NSError* error; + + CBLStringBytes directory; + CBLStringBytes header; + + C4LogFileOptions options {}; + + if (logSink) { + if (logSink.directory.length > 0 && ![self setupLogDirectory: logSink.directory error: &error]) { + CBLWarnError(Database, @"Cannot setup log directory at %@: %@", logSink.directory, error); + return; + } + + directory = CBLStringBytes(logSink.directory, false); + header = CBLStringBytes([CBLVersion userAgent], false); + + options = { + .base_path = directory, + .log_level = (C4LogLevel)logSink.level, + .max_rotate_count = static_cast(logSink.maxKeptFiles - 1), + .max_size_bytes = logSink.maxFileSize, + .use_plaintext = static_cast(logSink.usePlaintext), + .header = header + }; + } else { + options.log_level = kC4LogNone; + options.base_path = kFLSliceNull; + } + + C4Error c4err; + if (!c4log_writeToBinaryFile(options, &c4err)) { + convertError(c4err, &error); + CBLWarnError(Database, @"Cannot enable file logging: %@", error); + } +} + +- (void) writeLogWithLevel: (CBLLogLevel)level domain: (CBLLogDomain)domain message: (NSString*)message { + C4LogLevel c4level = (C4LogLevel)level; + C4LogDomain c4domain; + switch (domain) { + case kCBLLogDomainDatabase: + c4domain = kCBL_LogDomainDatabase; + break; + case kCBLLogDomainQuery: + c4domain = kCBL_LogDomainQuery; + break; + case kCBLLogDomainReplicator: + c4domain = kCBL_LogDomainSync; + break; + case kCBLLogDomainNetwork: + c4domain = kCBL_LogDomainWebSocket; + break; +#ifdef COUCHBASE_ENTERPRISE + case kCBLLogDomainListener: + c4domain = kCBL_LogDomainListener; + break; +#endif + default: + c4domain = kCBL_LogDomainDatabase; + } + CBLStringBytes c4msg(message); + c4slog(c4domain, c4level, c4msg); +} + ++ (BOOL) setupLogDirectory: (NSString*)directory error: (NSError**)outError { + NSError* error; + if (![[NSFileManager defaultManager] createDirectoryAtPath: directory + withIntermediateDirectories: YES + attributes: nil + error: &error]) { + if (!CBLIsFileExistsError(error)) { + if (outError) + *outError = error; + return NO; + } + } + return YES; +} + +@end diff --git a/Objective-C/LogSink/CBLLogSinks.h b/Objective-C/LogSink/CBLLogSinks.h new file mode 100644 index 000000000..7dd5d8cdc --- /dev/null +++ b/Objective-C/LogSink/CBLLogSinks.h @@ -0,0 +1,44 @@ +// +// CBLLogSinks.h +// CouchbaseLite +// +// Copyright (c) 2025 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +@class CBLConsoleLogSink; +@class CBLCustomLogSink; +@class CBLFileLogSink; + +NS_ASSUME_NONNULL_BEGIN + +/** A static container for managing the three log sinks used by Couchbase Lite. */ +@interface CBLLogSinks : NSObject + +/** The console log sink, enabled by default with a warning level. */ +@property (class, nonatomic, nullable) CBLConsoleLogSink* console; + +/** The file log sink, disabled by default. */ +@property (class, nonatomic, nullable) CBLFileLogSink* file; + +/** The custom log sink, disabled by default. */ +@property (class, nonatomic, nullable) CBLCustomLogSink* custom; + +- (instancetype) init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Objective-C/LogSink/CBLLogSinks.mm b/Objective-C/LogSink/CBLLogSinks.mm new file mode 100644 index 000000000..29732b7fc --- /dev/null +++ b/Objective-C/LogSink/CBLLogSinks.mm @@ -0,0 +1,205 @@ +// +// CBLLogSinks.mm +// CouchbaseLite +// +// Copyright (c) 2025 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#import "CBLLogSinks+Internal.h" +#import "CBLLogSinks+Reset.h" +#import "CBLLog+Logging.h" +#import "CBLStringBytes.h" + +C4LogDomain kCBL_LogDomainDatabase; +C4LogDomain kCBL_LogDomainQuery; +C4LogDomain kCBL_LogDomainSync; +C4LogDomain kCBL_LogDomainWebSocket; +C4LogDomain kCBL_LogDomainListener; + +static NSArray* c4Domains = @[@"DB", @"Query", @"Sync", @"WS", @"Listener"]; +static NSArray* platformDomains = @[@"BLIP", @"BLIPMessages", @"SyncBusy", @"TLS", @"Changes", @"Zip"]; + +static CBLLogLevel _domainsLevel = kCBLLogLevelNone; +static CBLLogLevel _callbackLevel = kCBLLogLevelNone; + +static CBLConsoleLogSink* _console = nil; +static CBLCustomLogSink* _custom = nil; +static CBLFileLogSink* _file = nil; + +// Note: +// +// This class is implemented with a minimum logging. The interface defines +// console, custom, and file property as nonatomic, so the expectation +// is not to set up the log sinks object concurrently. However, at least, +// the lock to access console and custom is required to avoid crash from accessing +// garbage objects as LiteCore's log callback may be called from a background thread. + +@implementation CBLLogSinks + +static CBLLogAPI _vAPI; +NSDictionary* domainDictionary = nil; + ++ (void) initialize { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // Cache log domain for logging from the platforms: + kCBL_LogDomainDatabase = c4log_getDomain("DB", true); + kCBL_LogDomainQuery = c4log_getDomain("Query", true); + kCBL_LogDomainSync = c4log_getDomain("Sync", true); + kCBL_LogDomainWebSocket = c4log_getDomain("WS", true); + kCBL_LogDomainListener = c4log_getDomain("Listener", true); + + // Create the default warning console log: + self.console = [[CBLConsoleLogSink alloc] initWithLevel: kCBLLogLevelWarning]; + + [self resetApiVersion]; + }); +} + ++ (void) setConsole:(nullable CBLConsoleLogSink*)console { + CBL_LOCK(self) { + [self checkLogApiVersion: console]; + _console = console; + } + [self updateLogLevels]; +} + ++ (CBLConsoleLogSink*) console { + CBL_LOCK(self) { + return _console; + } +} + ++ (void) setCustom: (nullable CBLCustomLogSink*) custom { + CBL_LOCK(self) { + [self checkLogApiVersion: custom]; + _custom = custom; + } + [self updateLogLevels]; +} + ++ (CBLCustomLogSink*) custom { + CBL_LOCK(self) { + return _custom; + } +} + ++ (void) setFile: (nullable CBLFileLogSink*) file { + CBL_LOCK(self) { + [self checkLogApiVersion: file]; + _file = file; + } + [CBLFileLogSink setup: file]; + [self updateLogLevels]; +} + ++ (CBLFileLogSink*) file { + CBL_LOCK(self) { + return _file; + } +} + ++ (void) updateLogLevels { + CBLConsoleLogSink* console = self.console; + CBLLogLevel consoleLevel = console != nil ? console.level : kCBLLogLevelNone; + + CBLCustomLogSink* custom = self.custom; + CBLLogLevel customLevel = custom.logSink != nil ? custom.level : kCBLLogLevelNone; + + CBLFileLogSink* file = self.file; + CBLLogLevel fileLevel = file != nil ? file.level : kCBLLogLevelNone; + + CBLLogLevel callbackLevel = std::min(consoleLevel, customLevel); + CBLLogLevel domainsLevel = std::min(callbackLevel, fileLevel); + C4LogLevel c4Level = (C4LogLevel)domainsLevel; + + if (_domainsLevel != domainsLevel) { + for (NSString* domain in c4Domains) { + C4LogDomain c4domain = c4log_getDomain([domain UTF8String], false); + if (c4domain) + c4log_setLevel(c4domain, c4Level); + } + + for (NSString* domain in platformDomains) { + C4LogDomain c4domain = c4log_getDomain([domain UTF8String], false); + if (c4domain) + c4log_setLevel(c4domain, c4Level); + } + _domainsLevel = domainsLevel; + } + + if (_callbackLevel != callbackLevel) { + c4log_writeToCallback(c4Level, &c4Callback, true); + _callbackLevel = callbackLevel; + } +} + +static void c4Callback(C4LogDomain c4domain, C4LogLevel c4level, const char *msg, va_list args) { + NSString* message = [NSString stringWithUTF8String: msg]; + CBLLogLevel level = (CBLLogLevel) c4level; + CBLLogDomain domain = toCBLLogDomain(c4domain); + [CBLLogSinks.console writeLogWithLevel: level domain: domain message :message]; + [CBLLogSinks.custom writeLogWithLevel: level domain: domain message :message]; +} + ++ (void) writeCBLLog: (C4LogDomain)c4domain level: (C4LogLevel)c4level message: (NSString*)message { + CBLLogLevel level = (CBLLogLevel) c4level; + CBLLogDomain domain = toCBLLogDomain(c4domain); + + [CBLLogSinks.console writeLogWithLevel: level domain: domain message :message]; + [CBLLogSinks.file writeLogWithLevel: level domain: domain message :message]; + [CBLLogSinks.custom writeLogWithLevel: level domain: domain message :message]; +} + + +static CBLLogDomain toCBLLogDomain(C4LogDomain domain) { + if (!domainDictionary) { + domainDictionary = @{ @"DB": @(kCBLLogDomainDatabase), + @"Query": @(kCBLLogDomainQuery), + @"Sync": @(kCBLLogDomainReplicator), + @"SyncBusy": @(kCBLLogDomainReplicator), + @"Changes": @(kCBLLogDomainDatabase), + @"BLIP": @(kCBLLogDomainNetwork), + @"WS": @(kCBLLogDomainNetwork), + @"BLIPMessages": @(kCBLLogDomainNetwork), + @"Zip": @(kCBLLogDomainNetwork), + @"TLS": @(kCBLLogDomainNetwork), +#ifdef COUCHBASE_ENTERPRISE + @"Listener": @(kCBLLogDomainListener) +#endif + }; + } + + NSString* domainName = [NSString stringWithUTF8String: c4log_getDomainName(domain)]; + NSNumber* mapped = [domainDictionary objectForKey: domainName]; + return mapped ? mapped.integerValue : kCBLLogDomainDatabase; +} + +#pragma mark - Internal + ++ (void) resetApiVersion { + _vAPI = kCBLLogAPINone; +} + ++ (void) checkLogApiVersion: (id) source { + CBLLogAPI version = source ? source.version : kCBLLogAPINew; + if (_vAPI == kCBLLogAPINone) { + _vAPI = version; + } else if (_vAPI != version) { + [NSException raise: NSInternalInconsistencyException + format: @"Cannot use both new and old Logging API simultaneously."]; + } +} + +@end diff --git a/Objective-C/LogSink/CBLLogTypes.h b/Objective-C/LogSink/CBLLogTypes.h new file mode 100644 index 000000000..c285c13ab --- /dev/null +++ b/Objective-C/LogSink/CBLLogTypes.h @@ -0,0 +1,46 @@ +// +// CBLLogTypes.h +// CouchbaseLite +// +// Copyright (c) 2025 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +NS_ASSUME_NONNULL_BEGIN + +/** Log domain options. */ +typedef NS_OPTIONS(NSUInteger, CBLLogDomain) { + kCBLLogDomainDatabase = 1 << 0, ///< Database domain. + kCBLLogDomainQuery = 1 << 1, ///< Query domain. + kCBLLogDomainReplicator = 1 << 2, ///< Replicator domain. + kCBLLogDomainNetwork = 1 << 3, ///< Network domain. +#ifdef COUCHBASE_ENTERPRISE + kCBLLogDomainListener = 1 << 4, ///< Listener domain. + kCBLLogDomainAll = kCBLLogDomainDatabase | kCBLLogDomainQuery | kCBLLogDomainReplicator | kCBLLogDomainNetwork | kCBLLogDomainListener, ///< All domains +#else + kCBLLogDomainAll = kCBLLogDomainDatabase | kCBLLogDomainQuery | kCBLLogDomainReplicator | kCBLLogDomainNetwork, ///< All domains +#endif +}; + +/** Log level. */ +typedef NS_ENUM(NSUInteger, CBLLogLevel) { + kCBLLogLevelDebug, ///< Debug log messages. Only present in debug builds of CouchbaseLite. + kCBLLogLevelVerbose, ///< Verbose log messages. + kCBLLogLevelInfo, ///< Informational log messages. + kCBLLogLevelWarning, ///< Warning log messages. + kCBLLogLevelError, ///< Error log messages. + kCBLLogLevelNone ///< Disabling log messages of a given log domain. +}; + +NS_ASSUME_NONNULL_END diff --git a/Objective-C/Tests/CBLTestCase.m b/Objective-C/Tests/CBLTestCase.m index c2f77a11e..94d1e7bd3 100644 --- a/Objective-C/Tests/CBLTestCase.m +++ b/Objective-C/Tests/CBLTestCase.m @@ -20,6 +20,7 @@ #import "CBLTestCase.h" #include "c4.h" #import "CollectionUtils.h" +#import "CBLLogSinks+Reset.h" #ifdef COUCHBASE_ENTERPRISE #define kDatabaseDirName @"CouchbaseLite_EE" @@ -58,9 +59,14 @@ - (void) setUp { @"Error deleting CouchbaseLite folder: %@", error); } [self initDB]; + + // Debug logging to be made before this, if any + [CBLLogSinks resetApiVersion]; } - (void) tearDown { + [CBLLogSinks resetApiVersion]; + if (_db) { NSError* error; Assert([_db close: &error], @"Failed to close db: %@", error); diff --git a/Objective-C/Tests/LogTest.m b/Objective-C/Tests/LogTest.m index 3f240a0c0..d4433f6bf 100644 --- a/Objective-C/Tests/LogTest.m +++ b/Objective-C/Tests/LogTest.m @@ -2,7 +2,7 @@ // LogTest.m // CouchbaseLite // -// Copyright (c) 2018 Couchbase, Inc All rights reserved. +// Copyright (c) 2025 Couchbase, Inc All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,25 +19,16 @@ #import "CBLTestCase.h" #import "CBLLog+Logging.h" -#import "CustomLogger.h" - -@interface FileLoggerBackup: NSObject - -@property (nonatomic, nullable) CBLLogFileConfiguration* config; - -@property (nonatomic) CBLLogLevel level; - -@end +#import "CBLTestCustomLogSink.h" @interface LogTest : CBLTestCase @end @implementation LogTest { - FileLoggerBackup* _backup; - CBLLogLevel _backupConsoleLevel; - CBLLogDomain _backupConsoleDomain; NSString* logFileDirectory; + CBLFileLogSink* _fileBackup; + CBLConsoleLogSink* _consoleBackup; } // TODO: Remove https://issues.couchbase.com/browse/CBL-3206 @@ -46,39 +37,21 @@ @implementation LogTest { - (void) setUp { [super setUp]; - [self backupLoggerConfig]; NSString* folderName = [NSString stringWithFormat: @"LogTestLogs_%d", arc4random()]; logFileDirectory = [NSTemporaryDirectory() stringByAppendingPathComponent: folderName]; + _fileBackup = CBLLogSinks.file; + _consoleBackup = CBLLogSinks.console; } - (void) tearDown { - [super tearDown]; [[NSFileManager defaultManager] removeItemAtPath: logFileDirectory error: nil]; - [self restoreLoggerConfig]; -} - -- (CBLLogFileConfiguration*) logFileConfig { - return [[CBLLogFileConfiguration alloc] initWithDirectory: logFileDirectory]; -} + CBLLogSinks.file = _fileBackup; + CBLLogSinks.console = _consoleBackup; -- (void) backupLoggerConfig { - _backup = [[FileLoggerBackup alloc] init]; - _backup.level = CBLDatabase.log.file.level; - _backup.config = CBLDatabase.log.file.config; - _backupConsoleLevel = CBLDatabase.log.console.level; - _backupConsoleDomain = CBLDatabase.log.console.domains; -} - -- (void) restoreLoggerConfig { - if (_backup) { - CBLDatabase.log.file.level = _backup.level; - CBLDatabase.log.file.config = _backup.config; - _backup = nil; - } - CBLDatabase.log.custom = nil; - CBLDatabase.log.console.level = _backupConsoleLevel; - CBLDatabase.log.console.domains = _backupConsoleDomain; - + CBLLogSinks.custom = nil; + _fileBackup = nil; + _consoleBackup = nil; + [super tearDown]; } - (NSArray*) getLogsInDirectory: (NSString*)directory @@ -121,45 +94,27 @@ - (void) writeAllLogs: (NSString*)string { CBLWarnError(Database, @"%@", string); } -- (BOOL) isKeywordPresentInAnyLog: (NSString*)keyword path: (NSString*)path { - NSArray* files = [self getLogsInDirectory: path properties: nil onlyInfoLogs: NO]; - NSError* error; - for (NSURL* url in files) { - NSString* contents = [NSString stringWithContentsOfURL: url - encoding: NSASCIIStringEncoding - error: &error]; - AssertNil(error); - if ([contents rangeOfString: keyword].location != NSNotFound) { - return YES; - } - } - return NO; -} - - (void) testCustomLoggingLevels { CBLLogInfo(Database, @"IGNORE"); - CustomLogger* customLogger = [[CustomLogger alloc] init]; - CBLDatabase.log.custom = customLogger; - for (NSUInteger i = 5; i >= 1; i--) { - [customLogger reset]; - customLogger.level = (CBLLogLevel)i; - CBLDatabase.log.custom = customLogger; + CBLTestCustomLogSink* logSink = [[CBLTestCustomLogSink alloc] init]; + CBLCustomLogSink* customSink = [[CBLCustomLogSink alloc] initWithLevel: (CBLLogLevel)i logSink: logSink]; + CBLLogSinks.custom = customSink; CBLLogVerbose(Database, @"TEST VERBOSE"); CBLLogInfo(Database, @"TEST INFO"); CBLWarn(Database, @"TEST WARNING"); CBLWarnError(Database, @"TEST ERROR"); - AssertEqual(customLogger.lines.count, 5 - i); + AssertEqual(logSink.lines.count, 5 - i); } } - (void) testFileLoggingLevels { - CBLLogFileConfiguration* config = [self logFileConfig]; - config.usePlainText = YES; - CBLDatabase.log.file.config = config; - for (NSUInteger i = 5; i >= 1; i--) { - CBLDatabase.log.file.level = (CBLLogLevel)i; + CBLLogSinks.file = [[CBLFileLogSink alloc] initWithLevel: (CBLLogLevel)i + directory: logFileDirectory + usePlaintext: YES + maxKeptFiles: kCBLDefaultFileLogSinkMaxKeptFiles + maxFileSize: kCBLDefaultLogFileMaxSize]; CBLLogVerbose(Database, @"TEST VERBOSE"); CBLLogInfo(Database, @"TEST INFO"); CBLWarn(Database, @"TEST WARNING"); @@ -167,10 +122,10 @@ - (void) testFileLoggingLevels { } NSError* error; - NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath: config.directory + NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath: logFileDirectory error: &error]; for (NSString* file in files) { - NSString* log = [config.directory stringByAppendingPathComponent: file]; + NSString* log = [logFileDirectory stringByAppendingPathComponent: file]; NSString* content = [NSString stringWithContentsOfFile: log encoding: NSUTF8StringEncoding error: &error]; @@ -191,12 +146,11 @@ - (void) testFileLoggingLevels { } - (void) testFileLoggingDefaultBinaryFormat { - CBLLogFileConfiguration* config = [self logFileConfig]; - CBLDatabase.log.file.config = config; - CBLDatabase.log.file.level = kCBLLogLevelInfo; + CBLLogSinks.file = [[CBLFileLogSink alloc] initWithLevel: kCBLLogLevelInfo + directory: logFileDirectory]; CBLLogInfo(Database, @"TEST INFO"); - NSArray* files = [self getLogsInDirectory: config.directory + NSArray* files = [self getLogsInDirectory: CBLLogSinks.file.directory properties: @[NSFileModificationDate] onlyInfoLogs: YES]; NSArray* sorted = [files sortedArrayUsingComparator: ^NSComparisonResult(NSURL* url1, @@ -227,18 +181,16 @@ - (void) testFileLoggingDefaultBinaryFormat { } - (void) testFileLoggingUsePlainText { - CBLLogFileConfiguration* config = [self logFileConfig]; - AssertEqual(config.usePlainText, kCBLDefaultLogFileUsePlaintext); - config.usePlainText = YES; - Assert(config.usePlainText); - CBLDatabase.log.file.config = config; - CBLDatabase.log.file.level = kCBLLogLevelInfo; - Assert(CBLDatabase.log.file.config.usePlainText); + CBLLogSinks.file = [[CBLFileLogSink alloc] initWithLevel: kCBLLogLevelInfo + directory: logFileDirectory + usePlaintext: YES + maxKeptFiles: kCBLDefaultFileLogSinkMaxKeptFiles + maxFileSize: kCBLDefaultLogFileMaxSize]; NSString* input = @"SOME TEST MESSAGE"; CBLLogInfo(Database, @"%@", input); - NSArray* files = [self getLogsInDirectory: config.directory + NSArray* files = [self getLogsInDirectory: CBLLogSinks.file.directory properties: @[NSFileModificationDate] onlyInfoLogs: YES]; NSArray* sorted = [files sortedArrayUsingComparator: ^NSComparisonResult(NSURL* url1, @@ -268,13 +220,11 @@ - (void) testFileLoggingUsePlainText { } - (void) testFileLoggingLogFilename { - CBLLogFileConfiguration* config = [self logFileConfig]; - CBLDatabase.log.file.config = config; - CBLDatabase.log.file.level = kCBLLogLevelDebug; + CBLLogSinks.file = [[CBLFileLogSink alloc] initWithLevel: kCBLLogLevelDebug directory: logFileDirectory]; NSString* regex = @"cbl_(debug|verbose|info|warning|error)_\\d+\\.cbllog"; NSPredicate* predicate = [NSPredicate predicateWithFormat: @"SELF MATCHES %@", regex]; - NSArray* files = [self getLogsInDirectory: config.directory properties: nil onlyInfoLogs: NO]; + NSArray* files = [self getLogsInDirectory: CBLLogSinks.file.directory properties: nil onlyInfoLogs: NO]; for (NSURL* file in files) { Assert([predicate evaluateWithObject: file.lastPathComponent]); } @@ -282,77 +232,63 @@ - (void) testFileLoggingLogFilename { - (void) testEnableAndDisableCustomLogging { CBLLogInfo(Database, @"IGNORE"); - CustomLogger* customLogger = [[CustomLogger alloc] init]; - customLogger.level = kCBLLogLevelNone; - CBLDatabase.log.custom = customLogger; + CBLTestCustomLogSink* logSink = [[CBLTestCustomLogSink alloc] init]; + CBLLogSinks.custom = [[CBLCustomLogSink alloc] initWithLevel: kCBLLogLevelNone logSink: logSink]; CBLLogVerbose(Database, @"TEST VERBOSE"); CBLLogInfo(Database, @"TEST INFO"); CBLWarn(Database, @"TEST WARNING"); CBLWarnError(Database, @"TEST ERROR"); - AssertEqual(customLogger.lines.count, 0); + AssertEqual(logSink.lines.count, 0); - customLogger.level = kCBLLogLevelVerbose; - CBLDatabase.log.custom = customLogger; + CBLLogSinks.custom = [[CBLCustomLogSink alloc] initWithLevel: kCBLLogLevelVerbose logSink: logSink]; CBLLogVerbose(Database, @"TEST VERBOSE"); CBLLogInfo(Database, @"TEST INFO"); CBLWarn(Database, @"TEST WARNING"); CBLWarnError(Database, @"TEST ERROR"); - AssertEqual(customLogger.lines.count, 4); + AssertEqual(logSink.lines.count, 4); } - (void) testFileLoggingMaxSize { - CBLLogFileConfiguration* config = [self logFileConfig]; - config.usePlainText = YES; - AssertEqual(config.maxSize, kCBLDefaultLogFileMaxSize); - AssertEqual(config.maxRotateCount, kCBLDefaultLogFileMaxRotateCount); - config.maxSize = 1024; - AssertEqual(config.maxSize, 1024); - config.maxRotateCount = 2; - AssertEqual(config.maxRotateCount, 2); - CBLDatabase.log.file.config = config; - CBLDatabase.log.file.level = kCBLLogLevelDebug; - AssertEqual(CBLDatabase.log.file.config.maxSize, 1024); - AssertEqual(CBLDatabase.log.file.config.maxRotateCount, 2); + CBLLogSinks.file = [[CBLFileLogSink alloc] initWithLevel: kCBLLogLevelInfo directory: logFileDirectory]; + AssertEqual(CBLLogSinks.file.maxFileSize, kCBLDefaultFileLogSinkMaxSize); + AssertEqual(CBLLogSinks.file.maxKeptFiles, kCBLDefaultFileLogSinkMaxKeptFiles); + AssertEqual(CBLLogSinks.file.usePlaintext, kCBLDefaultLogFileUsePlaintext); + CBLLogSinks.file = [[CBLFileLogSink alloc] initWithLevel: kCBLLogLevelDebug + directory: logFileDirectory + usePlaintext: YES + maxKeptFiles: 2 + maxFileSize: 1024]; + AssertEqual(CBLLogSinks.file.maxFileSize, 1024); + AssertEqual(CBLLogSinks.file.maxKeptFiles, 2); // this should create three files, as the 1KB + 1KB + extra ~400-500Bytes. [self writeOneKiloByteOfLog]; [self writeOneKiloByteOfLog]; - NSUInteger totalFilesShouldBeInDirectory = (CBLDatabase.log.file.config.maxRotateCount + 1) * 5; + NSUInteger totalFilesShouldBeInDirectory = CBLLogSinks.file.maxKeptFiles * 5; #if !DEBUG totalFilesShouldBeInDirectory = totalFilesShouldBeInDirectory - 1; #endif - NSArray* files = [self getLogsInDirectory: config.directory properties: nil onlyInfoLogs: NO]; + NSArray* files = [self getLogsInDirectory: CBLLogSinks.file.directory properties: nil onlyInfoLogs: NO]; AssertEqual(files.count, totalFilesShouldBeInDirectory); } -- (void) testFileLoggingDisableLogging { - CBLLogFileConfiguration* config = [self logFileConfig]; - config.usePlainText = YES; - CBLDatabase.log.file.config = config; - CBLDatabase.log.file.level = kCBLLogLevelNone; - - NSString* inputString = [[NSUUID UUID] UUIDString]; - [self writeAllLogs: inputString]; - - AssertFalse([self isKeywordPresentInAnyLog: inputString path: config.directory]); -} - - (void) testFileLoggingReEnableLogging { - CBLLogFileConfiguration* config = [self logFileConfig]; - config.usePlainText = YES; - CBLDatabase.log.file.config = config; - CBLDatabase.log.file.level = kCBLLogLevelNone; + CBLLogSinks.file = nil; + AssertNil(CBLLogSinks.file.directory); NSString* inputString = [[NSUUID UUID] UUIDString]; [self writeAllLogs: inputString]; - AssertFalse([self isKeywordPresentInAnyLog: inputString path: config.directory]); + AssertNil(CBLLogSinks.file.directory); - CBLDatabase.log.file.level = kCBLLogLevelVerbose; + CBLLogSinks.file = [[CBLFileLogSink alloc] initWithLevel: kCBLLogLevelVerbose + directory: logFileDirectory + usePlaintext: YES + maxKeptFiles: kCBLDefaultFileLogSinkMaxKeptFiles + maxFileSize: kCBLDefaultLogFileMaxSize]; [self writeAllLogs: inputString]; - - NSArray* files = [self getLogsInDirectory: config.directory properties: nil onlyInfoLogs: NO]; + NSArray* files = [self getLogsInDirectory: CBLLogSinks.file.directory properties: nil onlyInfoLogs: NO]; NSError* error; for (NSURL* url in files) { if ([url.lastPathComponent hasPrefix: @"cbl_debug_"]) { @@ -367,13 +303,13 @@ - (void) testFileLoggingReEnableLogging { } - (void) testFileLoggingHeader { - CBLLogFileConfiguration* config = [self logFileConfig]; - config.usePlainText = YES; - CBLDatabase.log.file.config = config; - CBLDatabase.log.file.level = kCBLLogLevelVerbose; - + CBLLogSinks.file = [[CBLFileLogSink alloc] initWithLevel: kCBLLogLevelVerbose + directory: logFileDirectory + usePlaintext: YES + maxKeptFiles: kCBLDefaultFileLogSinkMaxKeptFiles + maxFileSize: kCBLDefaultLogFileMaxSize]; [self writeOneKiloByteOfLog]; - NSArray* files = [self getLogsInDirectory: config.directory properties: nil onlyInfoLogs: NO]; + NSArray* files = [self getLogsInDirectory: CBLLogSinks.file.directory properties: nil onlyInfoLogs: NO]; NSError* error; for (NSURL* url in files) { NSString* contents = [NSString stringWithContentsOfURL: url @@ -393,11 +329,9 @@ - (void) testFileLoggingHeader { } - (void) testNonASCII { - CustomLogger* customLogger = [[CustomLogger alloc] init]; - customLogger.level = kCBLLogLevelVerbose; - CBLDatabase.log.custom = customLogger; - CBLDatabase.log.console.domains = kCBLLogDomainAll; - CBLDatabase.log.console.level = kCBLLogLevelVerbose; + CBLTestCustomLogSink* logSink = [[CBLTestCustomLogSink alloc] init]; + CBLLogSinks.custom = [[CBLCustomLogSink alloc] initWithLevel: kCBLLogLevelVerbose logSink: logSink]; + NSString* hebrew = @"מזג האוויר נחמד היום"; // The weather is nice today. CBLMutableDocument* document = [self createDocument: @"doc1"]; [document setString: hebrew forKey: @"hebrew"]; @@ -413,38 +347,41 @@ - (void) testNonASCII { AssertEqual([[rs allObjects] count], 1u); NSString* expectedHebrew = [NSString stringWithFormat: @"[{\"hebrew\":\"%@\"}]", hebrew]; BOOL found = NO; - for (NSString* line in customLogger.lines) { + for (NSString* line in logSink.lines) { if ([line containsString: expectedHebrew]) { found = YES; } } Assert(found); + + CBLLogSinks.custom = nil; } - (void) testPercentEscape { - CustomLogger* customLogger = [[CustomLogger alloc] init]; - customLogger.level = kCBLLogLevelInfo; - CBLDatabase.log.custom = customLogger; - CBLDatabase.log.console.domains = kCBLLogDomainAll; - - CBLDatabase.log.console.level = kCBLLogLevelInfo; + CBLTestCustomLogSink* logSink = [[CBLTestCustomLogSink alloc] init]; + CBLLogSinks.custom = [[CBLCustomLogSink alloc] initWithLevel: kCBLLogLevelInfo logSink: logSink]; + CBLLogInfo(Database, @"Hello %%s there"); BOOL found = NO; - for (NSString* line in customLogger.lines) { + for (NSString* line in logSink.lines) { if ([line containsString: @"Hello %s there"]) { found = YES; } } Assert(found); + + CBLLogSinks.custom = nil; } -#pragma clang diagnostic pop - -@end - -@implementation FileLoggerBackup +- (void) testUseBothApi { + CBLTestCustomLogSink* logSink = [[CBLTestCustomLogSink alloc] init]; + CBLLogSinks.custom = [[CBLCustomLogSink alloc] initWithLevel: kCBLLogLevelVerbose logSink: logSink]; + [self expectException: @"NSInternalInconsistencyException" in: ^{ + CBLDatabase.log.console.level = kCBLLogLevelVerbose; + }]; +} -@synthesize config=_config, level=_level; +#pragma clang diagnostic pop @end diff --git a/Objective-C/Tests/LogTestOld.m b/Objective-C/Tests/LogTestOld.m new file mode 100644 index 000000000..115dc1074 --- /dev/null +++ b/Objective-C/Tests/LogTestOld.m @@ -0,0 +1,493 @@ +// +// LogTestOld.m +// CouchbaseLite +// +// Copyright (c) 2018 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "CBLTestCase.h" +#import "CBLLog+Logging.h" + +@interface CustomLogger : NSObject + +@property (nonatomic) CBLLogLevel level; + +@property (nonatomic, readonly) NSArray* lines; + +- (void) reset; + +- (BOOL) containsString: (NSString *)string; + +@end + +@interface FileLoggerBackup: NSObject + +@property (nonatomic, nullable) CBLLogFileConfiguration* config; + +@property (nonatomic) CBLLogLevel level; + +@end + +@interface LogTestOld : CBLTestCase + +@end + +@implementation LogTestOld { + FileLoggerBackup* _backup; + CBLLogLevel _backupConsoleLevel; + CBLLogDomain _backupConsoleDomain; + NSString* logFileDirectory; +} + +// TODO: Remove https://issues.couchbase.com/browse/CBL-3206 +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +- (void) setUp { + [super setUp]; + NSString* folderName = [NSString stringWithFormat: @"LogTestLogs_%d", arc4random()]; + logFileDirectory = [NSTemporaryDirectory() stringByAppendingPathComponent: folderName]; + _backup = [[FileLoggerBackup alloc] init]; + _backup.level = CBLDatabase.log.file.level; + _backup.config = CBLDatabase.log.file.config; +} + +- (void) tearDown { + [[NSFileManager defaultManager] removeItemAtPath: logFileDirectory error: nil]; + + CBLDatabase.log.file.level = _backup.level; + CBLDatabase.log.file.config = _backup.config; + CBLDatabase.log.console.level = _backupConsoleLevel; + CBLDatabase.log.console.domains = _backupConsoleDomain; + + _backup = nil; + CBLDatabase.log.custom = nil; + [super tearDown]; +} + +- (CBLLogFileConfiguration*) logFileConfig { + return [[CBLLogFileConfiguration alloc] initWithDirectory: logFileDirectory]; +} + +- (NSArray*) getLogsInDirectory: (NSString*)directory + properties: (nullable NSArray*)keys + onlyInfoLogs: (BOOL)onlyInfo { + AssertNotNil(directory); + NSURL* path = [NSURL fileURLWithPath: directory]; + AssertNotNil(path); + + NSError* error; + NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtURL: path + includingPropertiesForKeys: keys ? keys : @[] + options: 0 + error: &error]; + NSString* format = @"pathExtension == 'cbllog'"; + if (onlyInfo) { + format = [NSString stringWithFormat: @"%@ && lastPathComponent BEGINSWITH 'cbl_info_'", format]; + } + NSPredicate* predicate = [NSPredicate predicateWithFormat: format]; + return [files filteredArrayUsingPredicate: predicate]; +} + +- (void) writeOneKiloByteOfLog { + NSString* inputString = @"11223344556677889900"; // 20B + (27B, 24B, 24B, 24B, 29B) ~44B line + for(int i = 0; i < 23; i++) { + CBLDebug(Database, @"%@", inputString); + CBLLogInfo(Database, @"%@", inputString); + CBLLogVerbose(Database, @"%@", inputString); + CBLWarn(Database, @"%@", inputString); + CBLWarnError(Database, @"%@", inputString); + } + [self writeAllLogs: @"-"]; // 25B : total ~1037Bytes +} + +- (void) writeAllLogs: (NSString*)string { + CBLDebug(Database, @"%@", string); + CBLLogInfo(Database, @"%@", string); + CBLLogVerbose(Database, @"%@", string); + CBLWarn(Database, @"%@", string); + CBLWarnError(Database, @"%@", string); +} + +- (BOOL) isKeywordPresentInAnyLog: (NSString*)keyword path: (NSString*)path { + NSArray* files = [self getLogsInDirectory: path properties: nil onlyInfoLogs: NO]; + NSError* error; + for (NSURL* url in files) { + NSString* contents = [NSString stringWithContentsOfURL: url + encoding: NSASCIIStringEncoding + error: &error]; + AssertNil(error); + if ([contents rangeOfString: keyword].location != NSNotFound) { + return YES; + } + } + return NO; +} + +- (void) testCustomLoggingLevels { + CBLLogInfo(Database, @"IGNORE"); + CustomLogger* customLogger = [[CustomLogger alloc] init]; + + for (NSUInteger i = 5; i >= 1; i--) { + [customLogger reset]; + customLogger.level = (CBLLogLevel)i; + CBLDatabase.log.custom = customLogger; + CBLLogVerbose(Database, @"TEST VERBOSE"); + CBLLogInfo(Database, @"TEST INFO"); + CBLWarn(Database, @"TEST WARNING"); + CBLWarnError(Database, @"TEST ERROR"); + AssertEqual(customLogger.lines.count, 5 - i); + } +} + +- (void) testFileLoggingLevels { + CBLLogFileConfiguration* config = [self logFileConfig]; + config.usePlainText = YES; + CBLDatabase.log.file.config = config; + + for (NSUInteger i = 5; i >= 1; i--) { + CBLDatabase.log.file.level = (CBLLogLevel)i; + CBLLogVerbose(Database, @"TEST VERBOSE"); + CBLLogInfo(Database, @"TEST INFO"); + CBLWarn(Database, @"TEST WARNING"); + CBLWarnError(Database, @"TEST ERROR"); + } + + NSError* error; + NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath: config.directory + error: &error]; + for (NSString* file in files) { + NSString* log = [config.directory stringByAppendingPathComponent: file]; + NSString* content = [NSString stringWithContentsOfFile: log + encoding: NSUTF8StringEncoding + error: &error]; + __block int lineCount = 0; + [content enumerateLinesUsingBlock: ^(NSString *line, BOOL *stop) { + lineCount++; + }]; + + if ([file rangeOfString: @"verbose"].location != NSNotFound) + AssertEqual(lineCount, 3); + else if ([file rangeOfString: @"info"].location != NSNotFound) + AssertEqual(lineCount, 4); + else if ([file rangeOfString: @"warning"].location != NSNotFound) + AssertEqual(lineCount, 5); + else if ([file rangeOfString: @"error"].location != NSNotFound) + AssertEqual(lineCount, 6); + } +} + +- (void) testFileLoggingDefaultBinaryFormat { + CBLLogFileConfiguration* config = [self logFileConfig]; + CBLDatabase.log.file.config = config; + CBLDatabase.log.file.level = kCBLLogLevelInfo; + + CBLLogInfo(Database, @"TEST INFO"); + NSArray* files = [self getLogsInDirectory: config.directory + properties: @[NSFileModificationDate] + onlyInfoLogs: YES]; + NSArray* sorted = [files sortedArrayUsingComparator: ^NSComparisonResult(NSURL* url1, + NSURL* url2) { + NSError* err; + NSDate *date1 = nil; + [url1 getResourceValue: &date1 + forKey: NSURLContentModificationDateKey + error: &err]; + + NSDate* date2 = nil; + [url2 getResourceValue: &date2 + forKey: NSURLContentModificationDateKey + error: &err]; + return [date1 compare: date2]; + }]; + + NSURL* last = [sorted lastObject]; + AssertNotNil(last); + + NSError* error; + NSFileHandle* sourceFileHandle = [NSFileHandle fileHandleForReadingFromURL: last error: &error]; + NSData* begainData = [sourceFileHandle readDataOfLength: 4]; + AssertNotNil(begainData); + Byte *bytes = (Byte *)[begainData bytes]; + Assert(bytes[0] == 0xcf && bytes[1] == 0xb2 && bytes[2] == 0xab && bytes[3] == 0x1b, + @"because the log should be in binary format"); +} + +- (void) testFileLoggingUsePlainText { + CBLLogFileConfiguration* config = [self logFileConfig]; + AssertEqual(config.usePlainText, kCBLDefaultLogFileUsePlaintext); + config.usePlainText = YES; + Assert(config.usePlainText); + CBLDatabase.log.file.config = config; + CBLDatabase.log.file.level = kCBLLogLevelInfo; + Assert(CBLDatabase.log.file.config.usePlainText); + + NSString* input = @"SOME TEST MESSAGE"; + CBLLogInfo(Database, @"%@", input); + + NSArray* files = [self getLogsInDirectory: config.directory + properties: @[NSFileModificationDate] + onlyInfoLogs: YES]; + NSArray* sorted = [files sortedArrayUsingComparator: ^NSComparisonResult(NSURL* url1, + NSURL* url2) { + NSError* err; + NSDate *date1 = nil; + [url1 getResourceValue: &date1 + forKey: NSURLContentModificationDateKey + error: &err]; + + NSDate* date2 = nil; + [url2 getResourceValue: &date2 + forKey: NSURLContentModificationDateKey + error: &err]; + return [date1 compare: date2]; + }]; + + NSURL* last = [sorted lastObject]; + AssertNotNil(last); + + + NSError* error; + NSString* contents = [NSString stringWithContentsOfURL: last + encoding: NSASCIIStringEncoding + error: &error]; + Assert([contents rangeOfString: input].location != NSNotFound); +} + +- (void) testFileLoggingLogFilename { + CBLLogFileConfiguration* config = [self logFileConfig]; + CBLDatabase.log.file.config = config; + CBLDatabase.log.file.level = kCBLLogLevelDebug; + + NSString* regex = @"cbl_(debug|verbose|info|warning|error)_\\d+\\.cbllog"; + NSPredicate* predicate = [NSPredicate predicateWithFormat: @"SELF MATCHES %@", regex]; + NSArray* files = [self getLogsInDirectory: config.directory properties: nil onlyInfoLogs: NO]; + for (NSURL* file in files) { + Assert([predicate evaluateWithObject: file.lastPathComponent]); + } +} + +- (void) testEnableAndDisableCustomLogging { + CBLLogInfo(Database, @"IGNORE"); + CustomLogger* customLogger = [[CustomLogger alloc] init]; + customLogger.level = kCBLLogLevelNone; + CBLDatabase.log.custom = customLogger; + CBLLogVerbose(Database, @"TEST VERBOSE"); + CBLLogInfo(Database, @"TEST INFO"); + CBLWarn(Database, @"TEST WARNING"); + CBLWarnError(Database, @"TEST ERROR"); + AssertEqual(customLogger.lines.count, 0); + + customLogger.level = kCBLLogLevelVerbose; + CBLDatabase.log.custom = customLogger; + CBLLogVerbose(Database, @"TEST VERBOSE"); + CBLLogInfo(Database, @"TEST INFO"); + CBLWarn(Database, @"TEST WARNING"); + CBLWarnError(Database, @"TEST ERROR"); + AssertEqual(customLogger.lines.count, 4); +} + +- (void) testFileLoggingMaxSize { + CBLLogFileConfiguration* config = [self logFileConfig]; + config.usePlainText = YES; + AssertEqual(config.maxSize, kCBLDefaultLogFileMaxSize); + AssertEqual(config.maxRotateCount, kCBLDefaultLogFileMaxRotateCount); + config.maxSize = 1024; + AssertEqual(config.maxSize, 1024); + config.maxRotateCount = 2; + AssertEqual(config.maxRotateCount, 2); + CBLDatabase.log.file.config = config; + CBLDatabase.log.file.level = kCBLLogLevelDebug; + AssertEqual(CBLDatabase.log.file.config.maxSize, 1024); + AssertEqual(CBLDatabase.log.file.config.maxRotateCount, 2); + + // this should create three files, as the 1KB + 1KB + extra ~400-500Bytes. + [self writeOneKiloByteOfLog]; + [self writeOneKiloByteOfLog]; + + NSUInteger totalFilesShouldBeInDirectory = (CBLDatabase.log.file.config.maxRotateCount + 1) * 5; +#if !DEBUG + totalFilesShouldBeInDirectory = totalFilesShouldBeInDirectory - 1; +#endif + NSArray* files = [self getLogsInDirectory: config.directory properties: nil onlyInfoLogs: NO]; + AssertEqual(files.count, totalFilesShouldBeInDirectory); +} + +- (void) testFileLoggingDisableLogging { + CBLLogFileConfiguration* config = [self logFileConfig]; + config.usePlainText = YES; + CBLDatabase.log.file.config = config; + CBLDatabase.log.file.level = kCBLLogLevelNone; + + NSString* inputString = [[NSUUID UUID] UUIDString]; + [self writeAllLogs: inputString]; + + AssertFalse([self isKeywordPresentInAnyLog: inputString path: config.directory]); +} + +- (void) testFileLoggingReEnableLogging { + CBLLogFileConfiguration* config = [self logFileConfig]; + config.usePlainText = YES; + CBLDatabase.log.file.config = config; + CBLDatabase.log.file.level = kCBLLogLevelNone; + + NSString* inputString = [[NSUUID UUID] UUIDString]; + [self writeAllLogs: inputString]; + + AssertFalse([self isKeywordPresentInAnyLog: inputString path: config.directory]); + + CBLDatabase.log.file.level = kCBLLogLevelVerbose; + [self writeAllLogs: inputString]; + + NSArray* files = [self getLogsInDirectory: config.directory properties: nil onlyInfoLogs: NO]; + NSError* error; + for (NSURL* url in files) { + if ([url.lastPathComponent hasPrefix: @"cbl_debug_"]) { + continue; + } + NSString* contents = [NSString stringWithContentsOfURL: url + encoding: NSASCIIStringEncoding + error: &error]; + AssertNil(error); + Assert([contents rangeOfString: inputString].location != NSNotFound); + } +} + +- (void) testFileLoggingHeader { + CBLLogFileConfiguration* config = [self logFileConfig]; + config.usePlainText = YES; + CBLDatabase.log.file.config = config; + CBLDatabase.log.file.level = kCBLLogLevelVerbose; + + [self writeOneKiloByteOfLog]; + NSArray* files = [self getLogsInDirectory: config.directory properties: nil onlyInfoLogs: NO]; + NSError* error; + for (NSURL* url in files) { + NSString* contents = [NSString stringWithContentsOfURL: url + encoding: NSASCIIStringEncoding + error: &error]; + NSAssert(!error, @"Error reading file: %@", [error localizedDescription]); + NSArray *lines = [contents componentsSeparatedByString:@"\n"]; + + // Check if the log file contains at least two lines + NSAssert(lines.count >= 2, @"log contents should have at least two lines: information and header section"); + NSString *secondLine = lines[1]; + + NSAssert([secondLine rangeOfString:@"CouchbaseLite/"].location != NSNotFound, @"Second line should contain 'CouchbaseLite/'"); + NSAssert([secondLine rangeOfString:@"Build/"].location != NSNotFound, @"Second line should contain 'Build/'"); + NSAssert([secondLine rangeOfString:@"Commit/"].location != NSNotFound, @"Second line should contain 'Commit/'"); + } +} + +- (void) testNonASCII { + CustomLogger* customLogger = [[CustomLogger alloc] init]; + customLogger.level = kCBLLogLevelVerbose; + CBLDatabase.log.custom = customLogger; + + NSString* hebrew = @"מזג האוויר נחמד היום"; // The weather is nice today. + CBLMutableDocument* document = [self createDocument: @"doc1"]; + [document setString: hebrew forKey: @"hebrew"]; + NSError* error; + [self.db saveDocument: document error: &error]; + AssertNil(error); + + CBLQuery* q = [CBLQueryBuilder select: @[[CBLQuerySelectResult all]] + from: [CBLQueryDataSource database: self.db]]; + AssertNotNil(q); + NSEnumerator* rs = [q execute:&error]; + AssertNil(error); + AssertEqual([[rs allObjects] count], 1u); + NSString* expectedHebrew = [NSString stringWithFormat: @"[{\"hebrew\":\"%@\"}]", hebrew]; + BOOL found = NO; + for (NSString* line in customLogger.lines) { + if ([line containsString: expectedHebrew]) { + found = YES; + } + } + Assert(found); +} + +- (void) testPercentEscape { + CustomLogger* customLogger = [[CustomLogger alloc] init]; + customLogger.level = kCBLLogLevelInfo; + CBLDatabase.log.custom = customLogger; + + CBLLogInfo(Database, @"Hello %%s there"); + + BOOL found = NO; + for (NSString* line in customLogger.lines) { + if ([line containsString: @"Hello %s there"]) { + found = YES; + } + } + Assert(found); +} + +- (void) testUseBothApi { + CustomLogger* customLogger = [[CustomLogger alloc] init]; + customLogger.level = kCBLLogLevelVerbose; + CBLDatabase.log.custom = customLogger; + [self expectException: @"NSInternalInconsistencyException" in: ^{ + CBLLogSinks.console = [[CBLConsoleLogSink alloc] initWithLevel: kCBLLogLevelVerbose]; + }]; +} + +#pragma clang diagnostic pop + +@end + +@implementation FileLoggerBackup + +@synthesize config=_config, level=_level; + +@end + +@implementation CustomLogger { + NSMutableArray* _lines; +} + +@synthesize level=_level; + +- (instancetype) init { + self = [super init]; + if (self) { + _level = kCBLLogLevelNone; + _lines = [NSMutableArray new]; + } + return self; +} + +- (NSArray*) lines { + return _lines; +} + +- (void) reset { + [_lines removeAllObjects]; +} + +- (BOOL) containsString: (NSString *)string { + for (NSString* line in _lines) { + if ([line containsString: string]) { + return YES; + } + } + return NO; +} + +- (void)logWithLevel: (CBLLogLevel)level domain: (CBLLogDomain)domain message: (NSString*)message { + [_lines addObject: message]; +} + +@end diff --git a/Objective-C/Tests/PartialIndexTest.m b/Objective-C/Tests/PartialIndexTest.m new file mode 100644 index 000000000..ef0cb39d6 --- /dev/null +++ b/Objective-C/Tests/PartialIndexTest.m @@ -0,0 +1,123 @@ +// +// PartialIndexTest.m +// CouchbaseLite +// +// Copyright (c) 2025 Couchbase, Inc. All rights reserved. +// +// Licensed under the Couchbase License Agreement (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// https://info.couchbase.com/rs/302-GJY-034/images/2017-10-30_License_Agreement.pdf +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "CBLTestCase.h" + +/** + Test Spec v1.0.3: + https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0007-Partial-Index.md + */ + +@interface PartialIndexTest : CBLTestCase + +@end + +@implementation PartialIndexTest + +/** + * 1. TestCreatePartialValueIndex + * + * Description + * Test that a partial value index is successfully created. + * + * Steps + * 1. Create a partial value index named "numIndex" in the default collection. + * - expression: "num" + * - where: "type = 'number'" + * 2. Check that the index is successfully created. + * 3. Create a query object with an SQL++ string: + * - SELECT * + * FROM _ + * WHERE type = 'number' AND num > 1000 + * 4. Get the query plan from the query object and check that the plan contains "USING INDEX numIndex" string. + * 5. Create a query object with an SQL++ string: + * - SELECT * + * FROM _ + * WHERE type = 'foo' AND num > 1000 + * 6. Get the query plan from the query object and check that the plan doesn't contain "USING INDEX numIndex" string. + */ +- (void) testCreatePartialValueIndex { + NSError* error; + CBLCollection* collection = [self.db defaultCollection: &error]; + + CBLValueIndexConfiguration* config = [[CBLValueIndexConfiguration alloc] initWithExpression: @[@"num"] where: @"type='number'"]; + Assert([collection createIndexWithName: @"numIndex" config: config error: nil]); + + NSString* sql = @"SELECT * FROM _ WHERE type = 'number' AND num > 1000"; + CBLQuery* q = [_db createQuery: sql error: &error]; + AssertNotNil(q); + NSString* explain = [q explain: &error]; + Assert([explain rangeOfString: @"USING INDEX numIndex"].location != NSNotFound); + + sql = @"SELECT * FROM _ WHERE type = 'foo' AND num > 1000"; + q = [_db createQuery: sql error: &error]; + explain = [q explain: &error]; + AssertFalse([explain rangeOfString: @"USING INDEX numIndex"].location != NSNotFound); +} + +/** + * 2. TestCreatePartialFullTextIndex + * + * Description + * Test that a partial full text index is successfully created. + * + * Steps + * 1. Create following two documents with the following bodies in the default collection. + * - { "content" : "Couchbase Lite is a database." } + * - { "content" : "Couchbase Lite is a NoSQL syncable database." } + * 2. Create a partial full text index named "contentIndex" in the default collection. + * - expression: "content" + * - where: "length(content) > 30" + * 3. Check that the index is successfully created. + * 4. Create a query object with an SQL++ string: + * - SELECT content + * FROM _ + * WHERE match(contentIndex, "database") + * 4. Execute the query and check that: + * - The query returns the second document. + * 5. Create a query object with an SQL++ string: + * - There is one result returned + * - The returned content is "Couchbase Lite is a NoSQL syncable database.". + */ +- (void) testCreatePartialFullTextIndex { + NSError* error; + CBLCollection* collection = [self.db defaultCollection: &error]; + NSString* json1 = @"{\"content\":\"Couchbase Lite is a database.\"}"; + NSString* json2 = @"{\"content\":\"Couchbase Lite is a NoSQL syncable database.\"}"; + + [collection saveDocument:[[CBLMutableDocument alloc] initWithJSON:json1 error:&error] + error:&error]; + + [collection saveDocument:[[CBLMutableDocument alloc] initWithJSON:json2 error:&error] + error:&error]; + + CBLFullTextIndexConfiguration* config = [[CBLFullTextIndexConfiguration alloc] initWithExpression: @[@"content"] + where: @"length(content)>30" + ignoreAccents: false + language: nil]; + Assert([collection createIndexWithName: @"contentIndex" config: config error: nil]); + + NSString* sql = @"SELECT content FROM _ WHERE match(contentIndex, 'database')"; + CBLQuery* q = [_db createQuery: sql error: &error]; + AssertNotNil(q); + NSArray* results = [[q execute: &error] allResults]; + AssertEqual(results.count, 1); + AssertEqualObjects([results[0] toJSON], json2); +} + +@end diff --git a/Objective-C/Tests/ReplicatorTest+CustomConflict.m b/Objective-C/Tests/ReplicatorTest+CustomConflict.m index 6acc96905..a43becb90 100644 --- a/Objective-C/Tests/ReplicatorTest+CustomConflict.m +++ b/Objective-C/Tests/ReplicatorTest+CustomConflict.m @@ -19,10 +19,9 @@ #import "ReplicatorTest.h" #import "CBLDocument+Internal.h" -#import "CustomLogger.h" -#import "CBLReplicator+Internal.h" #import "CBLErrorMessage.h" -#import "CBLDocument+Internal.h" +#import "CBLReplicator+Internal.h" +#import "CBLTestCustomLogSink.h" @interface ReplicatorTest_CustomConflict : ReplicatorTest @end @@ -52,12 +51,6 @@ - (void) testConflictResolverConfigProperty { AssertEqualObjects(config.conflictResolver, resolver); AssertNotNil(repl.config.conflictResolver); AssertEqualObjects(repl.config.conflictResolver, resolver); - -// memory leak with checking exception! -// // check whether conflict resolver can be edited after setting to replicator -// [self expectException: @"NSInternalInconsistencyException" in: ^{ -// repl.config.conflictResolver = nil; -// }]; } #pragma mark - Tests with replication @@ -449,9 +442,8 @@ - (void) testConflictResolverCalledTwice { - (void) testConflictResolverWrongDocID { // Enable Logging to check whether the logs are printing - CustomLogger* custom = [[CustomLogger alloc] init]; - custom.level = kCBLLogLevelWarning; - CBLDatabase.log.custom = custom; + CBLTestCustomLogSink* logSink = [[CBLTestCustomLogSink alloc] init]; + CBLLogSinks.custom = [[CBLCustomLogSink alloc] initWithLevel: kCBLLogLevelWarning logSink: logSink]; NSString* docId = @"doc"; NSDictionary* localData = @{@"key1": @"value1"}; @@ -496,9 +488,9 @@ - (void) testConflictResolverWrongDocID { NSString* warning = [NSString stringWithFormat: @"The document ID of the resolved document '%@'" " is not matching with the document ID of the conflicting document '%@'.", wrongDocID, docId]; - Assert([custom.lines containsObject: warning]); + Assert([logSink.lines containsObject: warning]); [replicator removeChangeListenerWithToken: token]; - CBLDatabase.log.custom = nil; + CBLLogSinks.custom = nil; } - (void) testConflictResolverDifferentDBDoc { @@ -823,9 +815,9 @@ - (void) testNonBlockingConflictResolver { // CBL-1710: Update to use setProgressLevel API in Replicator - (void) testDoubleConflictResolutionOnSameConflicts { NSString* docID = @"doc1"; - CustomLogger* custom = [[CustomLogger alloc] init]; - custom.level = kCBLLogLevelWarning; - CBLDatabase.log.custom = custom; + CBLTestCustomLogSink* logSink = [[CBLTestCustomLogSink alloc] init]; + CBLLogSinks.custom = [[CBLCustomLogSink alloc] initWithLevel: kCBLLogLevelWarning logSink: logSink]; + XCTestExpectation* expCCR = [self expectationWithDescription:@"wait for conflict resolver"]; XCTestExpectation* expSTOP = [self expectationWithDescription:@"wait for replicator to stop"]; XCTestExpectation* expFirstDocResolve = [self expectationWithDescription:@"wait for first conflict to resolve"]; @@ -888,13 +880,13 @@ - (void) testDoubleConflictResolutionOnSameConflicts { AssertEqualObjects([doc toDictionary], localData); // 7 - Assert([custom.lines containsObject: @"Unable to select conflicting revision for doc1, " + Assert([logSink.lines containsObject: @"Unable to select conflicting revision for doc1, " "the conflict may have been resolved..."]); [replicator removeChangeListenerWithToken: changeToken]; [replicator removeChangeListenerWithToken: docReplToken]; - CBLDatabase.log.custom = nil; + CBLLogSinks.custom = nil; } - (void) testConflictResolverReturningBlobFromDifferentDB { diff --git a/Objective-C/Tests/CustomLogger.h b/Objective-C/Tests/Util/CBLTestCustomLogSink.h similarity index 80% rename from Objective-C/Tests/CustomLogger.h rename to Objective-C/Tests/Util/CBLTestCustomLogSink.h index d39ce82e5..dead6e0c5 100644 --- a/Objective-C/Tests/CustomLogger.h +++ b/Objective-C/Tests/Util/CBLTestCustomLogSink.h @@ -1,8 +1,8 @@ // -// CustomLogger.h +// CBLTestCustomLogSink.h // CouchbaseLite // -// Copyright (c) 2019 Couchbase, Inc All rights reserved. +// Copyright (c) 2025 Couchbase, Inc All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,20 +18,18 @@ // #import -#import "CouchbaseLite.h" +#import "CBLCustomLogSink.h" NS_ASSUME_NONNULL_BEGIN -@interface CustomLogger : NSObject - -@property (nonatomic) CBLLogLevel level; +@interface CBLTestCustomLogSink : NSObject @property (nonatomic, readonly) NSArray* lines; -- (void) reset; - - (BOOL) containsString: (NSString *)string; +- (void) reset; + @end NS_ASSUME_NONNULL_END diff --git a/Objective-C/Tests/CustomLogger.m b/Objective-C/Tests/Util/CBLTestCustomLogSink.m similarity index 75% rename from Objective-C/Tests/CustomLogger.m rename to Objective-C/Tests/Util/CBLTestCustomLogSink.m index 20f73bfbf..b749cb916 100644 --- a/Objective-C/Tests/CustomLogger.m +++ b/Objective-C/Tests/Util/CBLTestCustomLogSink.m @@ -1,8 +1,8 @@ // -// CustomLogger.m +// CBLTestCustomLogSink.m // CouchbaseLite // -// Copyright (c) 2019 Couchbase, Inc All rights reserved. +// Copyright (c) 2025 Couchbase, Inc All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,18 +17,15 @@ // limitations under the License. // -#import "CustomLogger.h" +#import "CBLTestCustomLogSink.h" -@implementation CustomLogger { +@implementation CBLTestCustomLogSink { NSMutableArray* _lines; } -@synthesize level=_level; - - (instancetype) init { self = [super init]; if (self) { - _level = kCBLLogLevelNone; _lines = [NSMutableArray new]; } return self; @@ -38,10 +35,6 @@ - (NSArray*) lines { return _lines; } -- (void) reset { - [_lines removeAllObjects]; -} - - (BOOL) containsString: (NSString *)string { for (NSString* line in _lines) { if ([line containsString: string]) { @@ -51,8 +44,12 @@ - (BOOL) containsString: (NSString *)string { return NO; } -- (void)logWithLevel: (CBLLogLevel)level domain: (CBLLogDomain)domain message: (NSString*)message { - [_lines addObject: message]; +- (void) reset { + [_lines removeAllObjects]; +} + +- (void) writeLogWithLevel: (CBLLogLevel)level domain: (CBLLogDomain)domain message: (NSString*)message { + [_lines addObject: message]; } @end diff --git a/Objective-C/Tests/VectorSearchTest.h b/Objective-C/Tests/VectorSearchTest.h index 4d55eeb4b..8a4d6d41c 100644 --- a/Objective-C/Tests/VectorSearchTest.h +++ b/Objective-C/Tests/VectorSearchTest.h @@ -54,22 +54,22 @@ NS_ASSUME_NONNULL_BEGIN /** For the test subclasses to override the default vector expression. */ - (NSString*) wordsQueryDefaultExpression; -- (NSString*) wordsQueryStringWithLimit: (NSUInteger)limit +- (NSString*) wordsQueryStringWithLimit: (NSInteger)limit metric: (nullable NSString*)metric vectorExpression: (nullable NSString*)vectorExpression whereClause: (nullable NSString*)whereClause; -- (NSString*) wordsQueryStringWithLimit: (NSUInteger)limit; +- (NSString*) wordsQueryStringWithLimit: (NSInteger)limit; -- (CBLQueryResultSet*) executeWordsQueryWithLimit: (NSUInteger)limit +- (CBLQueryResultSet*) executeWordsQueryWithLimit: (NSInteger)limit metric: (nullable NSString*)metric vectorExpression: (nullable NSString*)vectorExpression whereClause: (nullable NSString*)whereClause checkTraining: (BOOL) checkTraining; -- (CBLQueryResultSet*) executeWordsQueryWithLimit: (NSUInteger)limit; +- (CBLQueryResultSet*) executeWordsQueryWithLimit: (NSInteger)limit; -- (CBLQueryResultSet*) executeWordsQueryNoTrainingCheckWithLimit: (NSUInteger)limit; +- (CBLQueryResultSet*) executeWordsQueryNoTrainingCheckWithLimit: (NSInteger)limit; - (NSDictionary*) toDocIDWordMap: (CBLQueryResultSet*)resultSet; diff --git a/Objective-C/Tests/VectorSearchTest.m b/Objective-C/Tests/VectorSearchTest.m index 265b6d127..3d53a61c7 100644 --- a/Objective-C/Tests/VectorSearchTest.m +++ b/Objective-C/Tests/VectorSearchTest.m @@ -19,7 +19,7 @@ #import "VectorSearchTest.h" #import "CBLWordEmbeddingModel.h" -#import "CustomLogger.h" +#import "CBLTestCustomLogSink.h" #define kDinnerVector @[@0.03193166106939316, @0.032055653631687164, @0.07188114523887634, @(-0.09893740713596344), @(-0.07693558186292648), @0.07570040225982666, @0.42786234617233276, @(-0.11442682892084122), @(-0.7863243818283081), @(-0.47983086109161377), @(-0.10168658196926117), @0.10985997319221497, @(-0.15261511504650116), @(-0.08458329737186432), @(-0.16363860666751862), @(-0.20225222408771515), @(-0.2593214809894562), @(-0.032738097012043), @(-0.16649988293647766), @(-0.059701453894376755), @0.17472036182880402, @(-0.007310086861252785), @(-0.13918264210224152), @(-0.07260780036449432), @(-0.02461239881813526), @(-0.04195880889892578), @(-0.15714778006076813), @0.48038315773010254, @0.7536261677742004, @0.41809454560279846, @(-0.17144775390625), @0.18296195566654205, @(-0.10611499845981598), @0.11669538915157318, @0.07423929125070572, @(-0.3105475902557373), @(-0.045081984251737595), @(-0.18190748989582062), @0.22430984675884247, @0.05735112354159355, @(-0.017394868656992912), @(-0.148889422416687), @(-0.20618586242198944), @(-0.1446581482887268), @0.061972495168447495, @0.07787969708442688, @0.14225411415100098, @0.20560632646083832, @0.1786964386701584, @(-0.380594402551651), @(-0.18301603198051453), @(-0.19542981684207916), @0.3879885971546173, @(-0.2219538390636444), @0.11549852043390274, @(-0.0021717497147619724), @(-0.10556972026824951), @0.030264658853411674, @0.16252967715263367, @0.06010117009282112, @(-0.045007310807704926), @0.02435707487165928, @0.12623260915279388, @(-0.12688252329826355), @(-0.3306281864643097), @0.06452160328626633,@0.0707000121474266, @(-0.04959108680486679), @(-0.2567063570022583), @(-0.01878536120057106), @(-0.10857286304235458), @(-0.01754194125533104), @(-0.0713721290230751), @0.05946013703942299, @(-0.1821729987859726), @(-0.07293688505887985), @(-0.2778160572052002), @0.17880073189735413, @(-0.04669278487563133), @0.05351974070072174, @(-0.23292849957942963), @0.05746332183480263, @0.15462779998779297, @(-0.04772235080599785), @(-0.003306782804429531), @0.058290787041187286, @0.05908169597387314, @0.00504430802538991, @(-0.1262340396642685), @0.11612161248922348, @0.25303348898887634, @0.18580256402492523, @0.09704313427209854, @(-0.06087183952331543), @0.19697663187980652, @(-0.27528849244117737), @(-0.0837797075510025), @(-0.09988483041524887), @(-0.20565757155418396), @0.020984146744012833, @0.031014855951070786, @0.03521743416786194, @(-0.05171370506286621), @0.009112107567489147, @(-0.19296088814735413), @(-0.19363830983638763), @0.1591167151927948, @(-0.02629968523979187), @(-0.1695055067539215), @(-0.35807400941848755), @(-0.1935291737318039), @(-0.17090126872062683), @(-0.35123637318611145), @(-0.20035606622695923), @(-0.03487539291381836), @0.2650701701641083, @(-0.1588021069765091), @0.32268261909484863, @(-0.024521857500076294), @(-0.11985184997320175), @0.14826008677482605, @0.194917231798172, @0.07971998304128647, @0.07594677060842514, @0.007186363451182842, @(-0.14641280472278595), @0.053229596465826035, @0.0619836151599884, @0.003207010915502906, @(-0.12729716300964355), @0.13496214151382446, @0.107656329870224, @(-0.16516226530075073), @(-0.033881571143865585), @(-0.11175122112035751), @(-0.005806141998618841), @(-0.4765360355377197), @0.11495379358530045, @0.1472187340259552, @0.3781401813030243, @0.10045770555734634, @(-0.1352398842573166), @(-0.17544329166412354), @(-0.13191302120685577), @(-0.10440415143966675), @0.34598618745803833, @0.09728766977787018, @(-0.25583627820014954), @0.035236816853284836, @0.16205145418643951, @(-0.06128586828708649), @0.13735555112361908, @0.11582338809967041, @(-0.10182418674230576), @0.1370954066514969, @0.15048766136169434, @0.06671152263879776, @(-0.1884871870279312), @(-0.11004580557346344), @0.24694739282131195, @(-0.008159132674336433), @(-0.11668405681848526), @(-0.01214478351175785), @0.10379738360643387, @(-0.1626262664794922), @0.09377897530794144, @0.11594484746456146, @(-0.19621512293815613), @0.26271334290504456, @0.04888357222080231, @(-0.10103251039981842), @0.33250945806503296, @0.13565145432949066, @(-0.23888370394706726), @(-0.13335271179676056), @(-0.0076894499361515045), @0.18256276845932007, @0.3276212215423584, @(-0.06567271053791046), @(-0.1853761374950409), @0.08945729583501816, @0.13876311480998993, @0.09976287186145782, @0.07869105041027069, @(-0.1346970647573471), @0.29857659339904785, @0.1329529583454132, @0.11350086331367493, @0.09112624824047089, @(-0.12515446543693542), @(-0.07917925715446472), @0.2881546914577484, @(-1.4532661225530319e-05), @(-0.07712751626968384), @0.21063975989818573, @0.10858846455812454, @(-0.009552721865475178), @0.1629313975572586, @(-0.39703384041786194), @0.1904662847518921, @0.18924959003925323, @(-0.09611514210700989), @0.001136621693149209, @(-0.1293390840291977), @(-0.019481558352708817), @0.09661063551902771, @(-0.17659670114517212), @0.11671938002109528, @0.15038564801216125, @(-0.020016824826598167), @(-0.20642194151878357), @0.09050136059522629, @(-0.1768183410167694), @(-0.2891409397125244), @0.04596589505672455, @(-0.004407480824738741), @0.15323616564273834, @0.16503025591373444, @0.17370983958244324, @0.02883041836321354, @0.1463884711265564, @0.14786243438720703, @(-0.026439940556883812), @(-0.03113352134823799), @0.10978181660175323, @0.008928884752094746, @0.24813824892044067, @(-0.06918247044086456), @0.06958142668008804, @0.17475970089435577, @0.04911438003182411, @0.17614248394966125, @0.19236832857131958, @(-0.1425514668226242), @(-0.056531358510255814), @(-0.03680772706866264), @(-0.028677923604846), @(-0.11353116482496262), @0.012293893843889236, @(-0.05192646384239197), @0.20331953465938568, @0.09290937334299088, @0.15373043715953827, @0.21684466302394867, @0.40546831488609314, @(-0.23753701150417328), @0.27929359674453735, @(-0.07277711480855942), @0.046813879162073135, @0.06883064657449722, @(-0.1033223420381546), @0.15769273042678833, @0.21685580909252167, @(-0.00971329677850008), @0.17375953495502472, @0.027193285524845123, @(-0.09943609684705734), @0.05770351365208626, @0.0868956446647644, @(-0.02671697922050953), @(-0.02979189157485962), @0.024517420679330826, @(-0.03931192681193352), @(-0.35641804337501526), @(-0.10590721666812897), @(-0.2118944674730301), @(-0.22070199251174927), @0.0941486731171608, @0.19881175458431244, @0.1815279871225357, @(-0.1256905049085617), @(-0.0683583989739418), @0.19080783426761627, @(-0.009482398629188538), @(-0.04374842345714569), @0.08184348791837692, @0.20070189237594604, @0.039221834391355515, @(-0.12251003831624985), @(-0.04325549304485321), @0.03840530663728714, @(-0.19840988516807556), @(-0.13591833412647247), @0.03073180839419365, @0.1059495136141777, @(-0.10656466335058212), @0.048937033861875534, @(-0.1362423598766327), @(-0.04138947278261185), @0.10234509408473969, @0.09793911874294281, @0.1391254961490631, @(-0.0906999260187149), @0.146945983171463, @0.14941848814487457, @0.23930180072784424, @0.36049938201904297, @0.0239607822149992, @0.08884347230195999, @0.061145078390836716] @@ -31,7 +31,7 @@ */ @implementation VectorSearchTest { - CustomLogger* _logger; + CBLTestCustomLogSink* _logSink; CBLDatabase* _wordDB; CBLDatabase* _modelDB; @@ -75,13 +75,12 @@ - (void) setUp { _wordDB = [self openDBNamed: kWordsDatabaseName error: &error]; AssertNotNil(_wordDB); - _logger = [[CustomLogger alloc] init]; - _logger.level = kCBLLogLevelInfo; - CBLDatabase.log.custom = _logger; + _logSink = [[CBLTestCustomLogSink alloc] init]; + CBLLogSinks.custom = [[CBLCustomLogSink alloc] initWithLevel: kCBLLogLevelInfo logSink: _logSink]; } - (void) tearDown { - CBLDatabase.log.custom = nil; + CBLLogSinks.custom = nil; _wordsCollection = nil; _extWordsCollection = nil; @@ -117,11 +116,11 @@ - (void) unregisterPredictiveModel { } - (void) resetIndexWasTrainedLog { - [_logger reset]; + [_logSink reset]; } - (BOOL) checkIndexWasTrained { - return ![_logger containsString: @"Untrained index; queries may be slow"]; + return ![_logSink containsString: @"Untrained index; queries may be slow"]; } - (void) createVectorIndexInCollection: (CBLCollection*)collection @@ -144,7 +143,7 @@ - (NSString*) wordsQueryDefaultExpression { return @"vector"; } -- (NSString*) wordsQueryStringWithLimit: (NSUInteger)limit +- (NSString*) wordsQueryStringWithLimit: (NSInteger)limit metric: (nullable NSString*)metric vectorExpression: (nullable NSString*)vectorExpression whereClause: (nullable NSString*)whereClause { @@ -164,17 +163,16 @@ - (NSString*) wordsQueryStringWithLimit: (NSUInteger)limit sql = [sql stringByAppendingFormat: @"ORDER BY APPROX_VECTOR_DISTANCE(%@, $vector) ", vectorExpression]; } - - sql = [sql stringByAppendingFormat: @"LIMIT %lu", (unsigned long)limit]; - + sql = [sql stringByAppendingFormat: @"LIMIT %ld", (long)limit]; + return sql; } -- (NSString*) wordsQueryStringWithLimit: (NSUInteger)limit { +- (NSString*) wordsQueryStringWithLimit: (NSInteger)limit { return [self wordsQueryStringWithLimit: limit metric: nil vectorExpression: nil whereClause: nil]; } -- (CBLQueryResultSet*) executeWordsQueryWithLimit: (NSUInteger)limit +- (CBLQueryResultSet*) executeWordsQueryWithLimit: (NSInteger)limit metric: (NSString*)metric vectorExpression: (NSString*)vectorExpression whereClause: (NSString*)whereClause @@ -204,16 +202,16 @@ - (CBLQueryResultSet*) executeWordsQueryWithLimit: (NSUInteger)limit return rs; } -- (CBLQueryResultSet*) executeWordsQueryWithLimit: (NSUInteger)limit { - return [self executeWordsQueryWithLimit: limit +- (CBLQueryResultSet*) executeWordsQueryWithLimit: (NSInteger)limit { + return [self executeWordsQueryWithLimit: limit metric: nil vectorExpression: nil whereClause: nil checkTraining: true]; } -- (CBLQueryResultSet*) executeWordsQueryNoTrainingCheckWithLimit: (NSUInteger)limit { - return [self executeWordsQueryWithLimit: limit +- (CBLQueryResultSet*) executeWordsQueryNoTrainingCheckWithLimit: (NSInteger)limit { + return [self executeWordsQueryWithLimit: limit metric: nil vectorExpression: nil whereClause: nil @@ -1178,10 +1176,10 @@ - (void) testVectorMatchLimitBoundary { CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); [self createWordsIndexWithConfig: config]; - // Check valid query with 1 and 10000 set limit - for (NSNumber* limit in @[@1, @10000]) { + // Check valid query with -1, 0, 1 and 10000 set limit + for (NSNumber* limit in @[@-1, @0, @1, @10000]) { NSError* error; - NSString* sql = [self wordsQueryStringWithLimit: [limit unsignedIntegerValue]]; + NSString* sql = [self wordsQueryStringWithLimit: [limit integerValue]]; Assert([self.wordDB createQuery: sql error: &error]); AssertNil(error); } diff --git a/Swift/ConsoleLogSink.swift b/Swift/ConsoleLogSink.swift new file mode 100644 index 000000000..69ebbc389 --- /dev/null +++ b/Swift/ConsoleLogSink.swift @@ -0,0 +1,35 @@ +// +// ConsoleLogSink.swift +// CouchbaseLite +// +// Copyright (c) 2025 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +/// A log sink that writes log messages to the console. +public struct ConsoleLogSink { + /// The minimum log level of the log messages to be logged. + public let level: LogLevel + + /// The set of log domains of the log messages to be logged. + public let domains: LogDomains + + /// Initializes a ConsoleLogSink with the specified log level and optional log domains. + public init(level: LogLevel, domains: LogDomains = .all) { + self.level = level + self.domains = domains + } +} diff --git a/Swift/ConsoleLogger.swift b/Swift/ConsoleLogger.swift index a95d7909a..6082eb9fd 100644 --- a/Swift/ConsoleLogger.swift +++ b/Swift/ConsoleLogger.swift @@ -33,9 +33,6 @@ public struct LogDomains: OptionSet { self.rawValue = rawValue } - /// All domains. - public static let all = LogDomains(rawValue: Int(LogDomain.all.rawValue)) - /// Database domain. public static let database = LogDomains(rawValue: Int(LogDomain.database.rawValue)) @@ -48,6 +45,15 @@ public struct LogDomains: OptionSet { /// Network domain. public static let network = LogDomains(rawValue: Int(LogDomain.network.rawValue)) +#if COUCHBASE_ENTERPRISE + /// Listener domain. + public static let listener = LogDomains(rawValue: Int(LogDomain.listener.rawValue)) + + /// All domains. + public static let all: LogDomains = [.database, .query, .replicator, .network, .listener] +#else + public static let all: LogDomains = [.database, .query, .replicator, .network] +#endif } public class ConsoleLogger { diff --git a/Swift/CouchbaseLiteSwift.private.modulemap b/Swift/CouchbaseLiteSwift.private.modulemap index ccd5fc695..75d74992e 100644 --- a/Swift/CouchbaseLiteSwift.private.modulemap +++ b/Swift/CouchbaseLiteSwift.private.modulemap @@ -31,6 +31,8 @@ framework module CouchbaseLiteSwift_Private { header "CBLConflict.h" header "CBLConflictResolver.h" header "CBLConsoleLogger.h" + header "CBLConsoleLogSink.h" + header "CBLCustomLogSink.h" header "CBLDatabase.h" header "CBLDatabaseChange.h" header "CBLDatabaseConfiguration.h" @@ -45,6 +47,7 @@ framework module CouchbaseLiteSwift_Private { header "CBLEndpoint.h" header "CBLErrors.h" header "CBLFileLogger.h" + header "CBLFileLogSink.h" header "CBLFragment.h" header "CBLFullTextIndex.h" header "CBLFullTextIndexConfiguration.h" @@ -56,6 +59,8 @@ framework module CouchbaseLiteSwift_Private { header "CBLLog.h" header "CBLLogFileConfiguration.h" header "CBLLogger.h" + header "CBLLogSinks.h" + header "CBLLogTypes.h" header "CBLQueryChange.h" header "CBLMutableArray.h" header "CBLMutableArrayFragment.h" @@ -101,6 +106,7 @@ framework module CouchbaseLiteSwift_Private { // Internal header "CBLQueryFullTextIndexExpression.h" + header "CBLLogSinks+Reset.h" // Swift Extensions: header "CBLArray+Swift.h" diff --git a/Swift/CustomLogSink.swift b/Swift/CustomLogSink.swift new file mode 100644 index 000000000..0da652b17 --- /dev/null +++ b/Swift/CustomLogSink.swift @@ -0,0 +1,51 @@ +// +// CustomLogSink.swift +// CouchbaseLite +// +// Copyright (c) 2025 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +/// Protocol for custom log sinks to handle log messages. +public protocol LogSinkProtocol { + /// Writes a log message with the given level, domain, and content. + func writeLog(level: LogLevel, domain: LogDomain, message: String) +} + +/// A log sink that writes log messages to a custom log sink implementation. +public struct CustomLogSink { + /// The minimum log level to be logged. + public let level: LogLevel + + /// The set of log domains of the log messages to be logged. + public let domains: LogDomains + + /// The custom log sink implementation. + public let logSink: LogSinkProtocol + + /// Initializes a ConsoleLogSink with the specified log level and the custom log sink implementation. + /// The default log domain is set to all domains. + public init(level: LogLevel, logSink: LogSinkProtocol) { + self.init(level: level, domains: .all, logSink: logSink) + } + + /// Initializes a ConsoleLogSink with the specified log level, log domains, and the custom log sink implementation. + public init(level: LogLevel, domains: LogDomains, logSink: LogSinkProtocol) { + self.level = level + self.logSink = logSink + self.domains = domains + } +} diff --git a/Swift/Defaults.swift b/Swift/Defaults.swift index 572143e4e..fd7c08fbc 100644 --- a/Swift/Defaults.swift +++ b/Swift/Defaults.swift @@ -49,6 +49,19 @@ public extension LogFileConfiguration { } +public extension FileLogSink { + + /// [false] Plaintext is not used, and instead binary encoding is used in log files + static let defaultUsePlaintext: Bool = false + + /// [2] 2 files preserved during each log rotation + static let defaultMaxKeptFiles: Int32 = 2 + + /// [524288] 512 KiB for the size of a log file + static let defaultMaxSize: Int64 = 524288 + +} + public extension FullTextIndexConfiguration { /// [false] Accents and ligatures are not ignored when indexing via full text search diff --git a/Swift/FileLogSink.swift b/Swift/FileLogSink.swift new file mode 100644 index 000000000..6c7d6fcb1 --- /dev/null +++ b/Swift/FileLogSink.swift @@ -0,0 +1,58 @@ +// +// FileLogSink.swift +// CouchbaseLite +// +// Copyright (c) 2025 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +/// A log sink that writes log messages to files. +public struct FileLogSink { + /// The minimum log level of the log messages to be logged. + public let level: LogLevel + + /// The directory where the log files will be stored. + public let directory: String + + /// To use plain text file format instead of the default binary format. + /// The default is ``defaultUsePlaintext`` + public let usePlaintext: Bool + + /// The maximum size of a log file before being rotated in bytes. + /// The default is ``defaultMaxKeptFiles`` + public let maxKeptFiles: Int32 + + /// The max number of rotated log files to keep. + /// The default value is ``defaultMaxSize`` + public let maxFileSize: Int64 + + /// Initializes a FileLogSink with the specified log level, directory, and optional parameters. + /// + /// - Parameters: + /// - level: The minimum log level for messages to be logged. + /// - directory: The directory where the log files will be stored. + /// - usePlainText: An optional flag indicating whether to use plain text format for the log files. Default is using the binary format. + /// - maxKeptFiles: An optional maximum number of rotated log files to keep. Default is `defaultMaxKeptFiles`. + /// - maxFileSize: An optional maximum size of a log file before being rotated in bytes. Default is `defaultMaxSize`. + public init(level: LogLevel, directory: String, usePlainText: Bool = defaultUsePlaintext, + maxKeptFiles: Int32 = defaultMaxKeptFiles, maxFileSize: Int64 = defaultMaxSize) { + self.level = level + self.directory = directory + self.usePlaintext = usePlainText + self.maxKeptFiles = maxKeptFiles + self.maxFileSize = maxFileSize + } +} diff --git a/Swift/IndexConfiguration.swift b/Swift/IndexConfiguration.swift index 48c4258a5..38348f8d7 100644 --- a/Swift/IndexConfiguration.swift +++ b/Swift/IndexConfiguration.swift @@ -2,7 +2,7 @@ // IndexConfiguration.swift // CouchbaseLite // -// Copyright (c) 2021 Couchbase, Inc All rights reserved. +// Copyright (c) 2025 Couchbase, Inc All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -28,6 +28,10 @@ public struct FullTextIndexConfiguration: IndexConfiguration, IndexConfigConvert /// Gets the expressions to use to create the index. public let expressions: [String] + /// A predicate expression defining conditions for indexing documents. + /// Only documents satisfying the predicate are included, enabling partial indexes. + public let `where`: String? + /// Set the true value to ignore accents/diacritical marks. /// The default value is ``FullTextIndexConfiguration.defaultIgnoreAccents``. public let ignoreAccents: Bool @@ -36,19 +40,25 @@ public struct FullTextIndexConfiguration: IndexConfiguration, IndexConfigConvert /// Setting the language code affects how word breaks and word stems are parsed. /// Without setting the value, the current locale's language will be used. Setting /// a nil or "" value to disable the language features. - public var language: String? + public let language: String? - /// Constructor for creating a full-text index by using an array of N1QL expression strings - public init(_ expressions: [String], ignoreAccents: Bool? = FullTextIndexConfiguration.defaultIgnoreAccents, language: String? = nil) { + /// Initializes a full-text index using an array of N1QL expression strings, with an optional where clause for partial indexing. + /// - Parameter expressions The array of expression strings. + /// - Parameter ignoreAccents Optional to ignore accents/diacritical marks. + /// - Parameter language Optional language code which is an ISO-639 language such as "en", "fr", etc. + /// - Parameter where Optional where clause for partial indexing. + /// - Returns The value index configuration object. + public init(_ expressions: [String], where: String? = nil, ignoreAccents: Bool? = FullTextIndexConfiguration.defaultIgnoreAccents, language: String? = nil) { self.expressions = expressions self.ignoreAccents = ignoreAccents ?? FullTextIndexConfiguration.defaultIgnoreAccents self.language = language + self.where = `where` } // MARK: Internal func toImpl() -> CBLIndexConfiguration { - return CBLFullTextIndexConfiguration(expression: expressions, ignoreAccents: ignoreAccents, language: language) + return CBLFullTextIndexConfiguration(expression: expressions, where: `where`, ignoreAccents: ignoreAccents, language: language) } } @@ -57,15 +67,24 @@ public struct ValueIndexConfiguration: IndexConfiguration, IndexConfigConvertabl /// Gets the expressions to use to create the index. public let expressions: [String] - /// Constructor for creating a value index by using an array of N1QL expression strings. - public init(_ expressions: [String]) { + + /// A predicate expression defining conditions for indexing documents. + /// Only documents satisfying the predicate are included, enabling partial indexes. + public let `where`: String? + + /// Initializes a value index using an array of N1QL expression strings, with an optional where clause for partial indexing. + /// - Parameter expressions The array of expression strings. + /// - Parameter where Optional where clause for partial indexing. + /// - Returns The value index configuration object. + public init(_ expressions: [String], where: String? = nil) { self.expressions = expressions + self.where = `where` } // MARK: Internal func toImpl() -> CBLIndexConfiguration { - return CBLValueIndexConfiguration(expression: expressions) + return CBLValueIndexConfiguration(expression: expressions, where: `where`) } } diff --git a/Swift/LogSinks.swift b/Swift/LogSinks.swift new file mode 100644 index 000000000..d96619575 --- /dev/null +++ b/Swift/LogSinks.swift @@ -0,0 +1,87 @@ +// +// LogSinks.swift +// CouchbaseLite +// +// Copyright (c) 2025 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import CouchbaseLiteSwift_Private + +/// A static container for managing the three log sinks used by Couchbase Lite. +public class LogSinks { + /// The console log sink, enabled by default with a warning level. + public static var console: ConsoleLogSink? = .init(level: .warning) { + didSet { + if let console = self.console { + let level = CBLLogLevel(rawValue: UInt(console.level.rawValue))! + let domains = CBLLogDomain(rawValue: UInt(console.domains.rawValue)) + CBLLogSinks.console = CBLConsoleLogSink(level: level, domains: domains) + } else { + CBLLogSinks.console = nil + } + } + } + + /// The file log sink, disabled by default. + public static var file: FileLogSink? = nil { + didSet { + if let file = self.file { + let level = CBLLogLevel(rawValue: UInt(file.level.rawValue))! + CBLLogSinks.file = CBLFileLogSink(level: level, + directory: file.directory, + usePlaintext: file.usePlaintext, + maxKeptFiles: Int(file.maxKeptFiles), + maxFileSize: file.maxFileSize) + } else { + CBLLogSinks.file = nil + } + } + } + + /// The custom log sink, disabled by default. + public static var custom: CustomLogSink? = nil { + didSet { + if let custom = self.custom { + let level = CBLLogLevel(rawValue: UInt(custom.level.rawValue))! + let domains = CBLLogDomain(rawValue: UInt(custom.domains.rawValue)) + let logSink = CustomLogSinkBridge(logSink: custom.logSink) + CBLLogSinks.custom = CBLCustomLogSink(level: level, domains: domains, logSink: logSink) + } else { + CBLLogSinks.custom = nil + } + } + } + + /// For bridging between swift and objective custom log sink. + private class CustomLogSinkBridge : NSObject, CBLLogSinkProtocol { + let logSink: LogSinkProtocol + + init(logSink: LogSinkProtocol) { + self.logSink = logSink + } + + func writeLog(with level: CBLLogLevel, domain: CBLLogDomain, message: String) { + let logLevel = LogLevel.init(rawValue: UInt8(level.rawValue))! + let logDomain = LogDomain.init(rawValue: UInt8(domain.rawValue))! + logSink.writeLog(level:logLevel, domain: logDomain, message: message) + } + } + + /// Internally used for testing purpose. + static func _resetApiVersion() { + CBLLogSinks.resetApiVersion() + } +} diff --git a/Swift/Logger.swift b/Swift/Logger.swift index 51192114e..34975b117 100644 --- a/Swift/Logger.swift +++ b/Swift/Logger.swift @@ -21,14 +21,12 @@ import Foundation /// Log domain. /// -/// all: All log domains. /// database: Database domain. /// query: Query domain. /// replicator: Replicator domain. /// network: Network domain. /// listener: Listener domain. public enum LogDomain: UInt8 { - case all = 31 case database = 1 case query = 2 case replicator = 4 diff --git a/Swift/Tests/CBLTestCase.swift b/Swift/Tests/CBLTestCase.swift index 5aa06eace..173ee04b5 100644 --- a/Swift/Tests/CBLTestCase.swift +++ b/Swift/Tests/CBLTestCase.swift @@ -19,7 +19,7 @@ import XCTest import Foundation -import CouchbaseLiteSwift +@testable import CouchbaseLiteSwift extension String { func toJSONObj() -> Any { @@ -90,6 +90,8 @@ class CBLTestCase: XCTestCase { XCTAssertTrue(!FileManager.default.fileExists(atPath: self.directory)) try! initDB() + + LogSinks._resetApiVersion() } override func tearDown() { @@ -98,6 +100,8 @@ class CBLTestCase: XCTestCase { try! db.close() try! otherDB?.close() + LogSinks._resetApiVersion() + super.tearDown() } diff --git a/Swift/Tests/LogSinkTest.swift b/Swift/Tests/LogSinkTest.swift new file mode 100644 index 000000000..bb66fcf60 --- /dev/null +++ b/Swift/Tests/LogSinkTest.swift @@ -0,0 +1,453 @@ +// +// LogSinkTest.swift +// CouchbaseLite +// +// Copyright (c) 2025 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +@testable import CouchbaseLiteSwift + +class LogSinkTest: CBLTestCase { + + class TestCustomLogSink: LogSinkProtocol { + var lines: [String] = [] + + var level: LogLevel = .none + + func writeLog(level: LogLevel, domain: LogDomain, message: String) { + lines.append(message) + } + + func containsString(_ string: String) -> Bool { + for line in lines { + if (line as NSString).contains(string) { + return true + } + } + return false + } + } + + var logFileDirectory: String! + + var backupConsoleLogSink: ConsoleLogSink? + + var backupFileLogSink: FileLogSink? + + var backupCustomLogSink: CustomLogSink? + + override func setUp() { + super.setUp() + let folderName = "LogTestLogs_\(Int.random(in: 1...1000))" + logFileDirectory = (NSTemporaryDirectory() as NSString).appendingPathComponent(folderName) + backupLoggerConfig() + } + + override func tearDown() { + super.tearDown() + try? FileManager.default.removeItem(atPath: logFileDirectory) + restoreLoggerConfig() + } + + func logFileConfig() -> LogFileConfiguration { + return LogFileConfiguration(directory: logFileDirectory) + } + + func backupLoggerConfig() { + backupConsoleLogSink = LogSinks.console + backupFileLogSink = LogSinks.file + backupCustomLogSink = LogSinks.custom + } + + func restoreLoggerConfig() { + LogSinks.console = backupConsoleLogSink + LogSinks.file = backupFileLogSink + LogSinks.custom = backupCustomLogSink + } + + func getLogsInDirectory(_ directory: String, + properties: [URLResourceKey] = [], + onlyInfoLogs: Bool = false) throws -> [URL] + { + let url = URL(fileURLWithPath: directory) + let files = try FileManager.default.contentsOfDirectory(at: url, + includingPropertiesForKeys: properties, + options: .skipsSubdirectoryDescendants) + return files.filter({ $0.pathExtension == "cbllog" && + (onlyInfoLogs ? $0.lastPathComponent.starts(with: "cbl_info_") : true) }) + } + + func writeOneKiloByteOfLog() { + let message = "11223344556677889900" // 44Byte line + for _ in 0..<23 { // 1012 Bytes + Log.log(domain: .database, level: .error, message: "\(message)") + Log.log(domain: .database, level: .warning, message: "\(message)") + Log.log(domain: .database, level: .info, message: "\(message)") + Log.log(domain: .database, level: .verbose, message: "\(message)") + Log.log(domain: .database, level: .debug, message: "\(message)") + } + writeAllLogs("1") // ~25Bytes + } + + func writeAllLogs(_ message: String) { + Log.log(domain: .database, level: .error, message: message) + Log.log(domain: .database, level: .warning, message: message) + Log.log(domain: .database, level: .info, message: message) + Log.log(domain: .database, level: .verbose, message: message) + Log.log(domain: .database, level: .debug, message: message) + } + + func isKeywordPresentInAnyLog(_ keyword: String, path: String) throws -> Bool { + for file in try getLogsInDirectory(path) { + let contents = try String(contentsOf: file, encoding: .ascii) + if contents.contains(keyword) { + return true + } + } + return false + } + + func testDefaltLogSinks() throws { + XCTAssertNotNil(LogSinks.console) + XCTAssertEqual(LogSinks.console?.level, .warning) + XCTAssertEqual(LogSinks.console?.domains, .all) + XCTAssertNil(LogSinks.custom) + XCTAssertNil(LogSinks.file) + } + + func testFileLogSinkProperties() throws { + var logSink = FileLogSink(level: .info, directory: logFileDirectory) + XCTAssertEqual(logSink.level, .info) + XCTAssertEqual(logSink.directory, logFileDirectory) + XCTAssertFalse(logSink.usePlaintext) + XCTAssertEqual(logSink.maxKeptFiles, FileLogSink.defaultMaxKeptFiles) + XCTAssertEqual(logSink.maxFileSize, FileLogSink.defaultMaxSize) + + logSink = FileLogSink(level: .verbose, directory: logFileDirectory, + usePlainText: true, maxKeptFiles: 10, maxFileSize: 2048) + XCTAssertEqual(logSink.level, .verbose) + XCTAssertEqual(logSink.directory, logFileDirectory) + XCTAssertTrue(logSink.usePlaintext) + XCTAssertEqual(logSink.maxKeptFiles, 10) + XCTAssertEqual(logSink.maxFileSize, 2048) + } + + func testFileLogSinkLogLevels() throws { + for i in (1...5).reversed() { + let level = LogLevel(rawValue: UInt8(i))! + LogSinks.file = FileLogSink(level: level, directory: logFileDirectory, usePlainText: true) + Log.log(domain: .database, level: .verbose, message: "TEST VERBOSE") + Log.log(domain: .database, level: .info, message: "TEST INFO") + Log.log(domain: .database, level: .warning, message: "TEST WARNING") + Log.log(domain: .database, level: .error, message: "TEST ERROR") + } + + let files = try FileManager.default.contentsOfDirectory(atPath: logFileDirectory) + for file in files { + let log = (logFileDirectory as NSString).appendingPathComponent(file) + let content = try NSString(contentsOfFile: log, encoding: String.Encoding.utf8.rawValue) + + var lineCount = 0 + content.enumerateLines { (line, stop) in + lineCount = lineCount + 1 + } + + let sfile = file as NSString + if sfile.range(of: "verbose").location != NSNotFound { + XCTAssertEqual(lineCount, 3) + } else if sfile.range(of: "info").location != NSNotFound { + XCTAssertEqual(lineCount, 4) + } else if sfile.range(of: "warning").location != NSNotFound { + XCTAssertEqual(lineCount, 5) + } else if sfile.range(of: "error").location != NSNotFound { + XCTAssertEqual(lineCount, 6) + } + } + } + + func testFileLogSinkBinaryFormat() throws { + LogSinks.file = FileLogSink(level: .info, directory: logFileDirectory, usePlainText: false) + + Log.log(domain: .database, level: .info, message: "TEST INFO") + + let files = try getLogsInDirectory(logFileDirectory, + properties: [.contentModificationDateKey], + onlyInfoLogs: true) + let sorted = files.sorted { (url1, url2) -> Bool in + guard let date1 = try! url1 + .resourceValues(forKeys: [.contentModificationDateKey]) + .contentModificationDate + else { + fatalError("modification date is missing for the URL") + } + guard let date2 = try! url2 + .resourceValues(forKeys: [.contentModificationDateKey]) + .contentModificationDate + else { + fatalError("modification date is missing for the URL") + } + return date1.compare(date2) == .orderedAscending + } + + guard let last = sorted.last else { + fatalError("last item shouldn't be empty") + } + let handle = try FileHandle.init(forReadingFrom: last) + let data = handle.readData(ofLength: 4) + let bytes = [UInt8](data) + XCTAssert(bytes[0] == 0xcf && bytes[1] == 0xb2 && bytes[2] == 0xab && bytes[3] == 0x1b, + "because the log should be in binary format"); + } + + func testFileLogSinkPlainTextFormat() throws { + LogSinks.file = FileLogSink(level: .info, directory: logFileDirectory, usePlainText: true) + + let inputString = "SOME TEST INFO" + Log.log(domain: .database, level: .info, message: inputString) + + let files = try getLogsInDirectory(logFileDirectory, + properties: [.contentModificationDateKey], + onlyInfoLogs: true) + let sorted = files.sorted { (url1, url2) -> Bool in + guard let date1 = try! url1 + .resourceValues(forKeys: [.contentModificationDateKey]) + .contentModificationDate + else { + fatalError("modification date is missing for the URL") + } + guard let date2 = try! url2 + .resourceValues(forKeys: [.contentModificationDateKey]) + .contentModificationDate + else { + fatalError("modification date is missing for the URL") + } + return date1.compare(date2) == .orderedAscending + } + + guard let last = sorted.last else { + fatalError("last item shouldn't be empty") + } + + let contents = try String(contentsOf: last, encoding: .ascii) + XCTAssert(contents.contains(inputString)) + } + + func testFileLogSinkFilename() throws { + LogSinks.file = FileLogSink(level: .debug, directory: logFileDirectory, usePlainText: true) + let regex = "cbl_(debug|verbose|info|warning|error)_\\d+\\.cbllog" + let predicate = NSPredicate(format: "SELF MATCHES %@", regex) + for file in try getLogsInDirectory(logFileDirectory) { + XCTAssert(predicate.evaluate(with: file.lastPathComponent)) + } + } + + func testFileLogSinkMaxSize() throws { + LogSinks.file = FileLogSink(level: .debug, + directory: logFileDirectory, + usePlainText: true, + maxKeptFiles: 3, + maxFileSize: 1024) + + // This should create three files(per level) => 2KB logs + extra + writeOneKiloByteOfLog() + writeOneKiloByteOfLog() + + let totalFilesShouldBeInDirectory = LogSinks.file!.maxKeptFiles * 5 + let totalLogFilesSaved = try getLogsInDirectory(logFileDirectory) + XCTAssertEqual(totalLogFilesSaved.count, Int(totalFilesShouldBeInDirectory)) + } + + func testDisableFileLogSink() throws { + LogSinks.file = FileLogSink(level: .none, + directory: logFileDirectory, + usePlainText: true, + maxKeptFiles: 3, + maxFileSize: 1024) + let message = UUID().uuidString + writeAllLogs(message) + XCTAssertFalse(try isKeywordPresentInAnyLog(message, path: logFileDirectory)) + + LogSinks.file = nil + writeAllLogs(message) + XCTAssertFalse(try isKeywordPresentInAnyLog(message, path: logFileDirectory)) + } + + func testReEnableFileLogSink() throws { + LogSinks.file = FileLogSink(level: .verbose, + directory: logFileDirectory, + usePlainText: true) + + // Disable: + LogSinks.file = FileLogSink(level: .none, + directory: logFileDirectory, + usePlainText: true) + + let message = UUID().uuidString + writeAllLogs(message) + XCTAssertFalse(try isKeywordPresentInAnyLog(message, path: logFileDirectory)) + + // Reenable: + LogSinks.file = FileLogSink(level: .verbose, + directory: logFileDirectory, + usePlainText: true) + + writeAllLogs(message) + + for file in try getLogsInDirectory(logFileDirectory) { + if file.lastPathComponent.starts(with: "cbl_debug_") { + continue + } + let contents = try String(contentsOf: file, encoding: .ascii) + XCTAssert(contents.contains(message)) + } + } + + func testLogFileHeader() throws { + LogSinks.file = FileLogSink(level: .verbose, + directory: logFileDirectory, + usePlainText: true) + + writeOneKiloByteOfLog() + for file in try getLogsInDirectory(logFileDirectory) { + let contents = try String(contentsOf: file, encoding: .ascii) + let lines = contents.components(separatedBy: "\n") + + // Check if the log file contains at least two lines + guard lines.count >= 2 else { + fatalError("log contents should have at least two lines: information and header section") + } + let secondLine = lines[1] + + XCTAssert(secondLine.contains("CouchbaseLite/")) + XCTAssert(secondLine.contains("Build/")) + XCTAssert(secondLine.contains("Commit/")) + } + } + + func testConsoleLogSinkProperties() throws { + LogSinks.console = ConsoleLogSink(level: .verbose) + XCTAssertEqual(LogSinks.console?.level, .verbose) + XCTAssertEqual(LogSinks.console?.domains, .all) + + LogSinks.console = ConsoleLogSink(level: .info, domains: .replicator) + XCTAssertEqual(LogSinks.console?.level, .info) + XCTAssertEqual(LogSinks.console?.domains, .replicator) + } + + func testCustomLogSinkProperties() throws { + var logSink = TestCustomLogSink() + LogSinks.custom = CustomLogSink(level: .verbose, logSink: logSink) + XCTAssertEqual(LogSinks.custom?.level, .verbose) + XCTAssert(LogSinks.custom?.logSink as? TestCustomLogSink === logSink) + XCTAssertEqual(LogSinks.custom?.domains, .all) + + LogSinks.custom = CustomLogSink(level: .info, domains: .replicator, logSink: logSink) + XCTAssertEqual(LogSinks.custom?.level, .info) + XCTAssertEqual(LogSinks.custom?.domains, .replicator) + XCTAssert(LogSinks.custom?.logSink as? TestCustomLogSink === logSink) + } + + func testEnableDisableCustomLogSink() throws { + var logSink = TestCustomLogSink() + LogSinks.custom = CustomLogSink(level: .verbose, logSink: logSink) + Log.log(domain: .database, level: .verbose, message: "TEST VERBOSE") + Log.log(domain: .database, level: .info, message: "TEST INFO") + Log.log(domain: .database, level: .warning, message: "TEST WARNING") + Log.log(domain: .database, level: .error, message: "TEST ERROR") + XCTAssertEqual(logSink.lines.count, 4) + + logSink = TestCustomLogSink() + LogSinks.custom = CustomLogSink(level: .none, logSink: logSink) + Log.log(domain: .database, level: .verbose, message: "TEST VERBOSE") + Log.log(domain: .database, level: .info, message: "TEST INFO") + Log.log(domain: .database, level: .warning, message: "TEST WARNING") + Log.log(domain: .database, level: .error, message: "TEST ERROR") + XCTAssertEqual(logSink.lines.count, 0) + } + + func testCustomLogSinkLevels() throws { + Log.log(domain: .database, level: .info, message: "IGNORE") + for i in (1...5).reversed() { + let level = LogLevel(rawValue: UInt8(i))! + let logSink = TestCustomLogSink() + LogSinks.custom = CustomLogSink(level: level, logSink: logSink) + Log.log(domain: .database, level: .verbose, message: "TEST VERBOSE") + Log.log(domain: .database, level: .info, message: "TEST INFO") + Log.log(domain: .database, level: .warning, message: "TEST WARNING") + Log.log(domain: .database, level: .error, message: "TEST ERROR") + XCTAssertEqual(logSink.lines.count, 5 - i) + } + } + + func testCustomLogSinkDomains() throws { + let domains: [LogDomains] = [.database, .query, .replicator, .network] + let names = ["database", "query", "replicator", "network"] + + // Single Domain + for i in 0.. 2KB logs + extra writeOneKiloByteOfLog() writeOneKiloByteOfLog() - guard (Database.log.file.config?.maxRotateCount) != nil else { - fatalError("Config should be present!!") - } var totalFilesShouldBeInDirectory = 15 /* (maxRotateCount + 1) * 5(levels) */ - - #if !DEBUG - totalFilesShouldBeInDirectory = totalFilesShouldBeInDirectory - 1 - #endif - let totalLogFilesSaved = try getLogsInDirectory(config.directory) XCTAssertEqual(totalLogFilesSaved.count, totalFilesShouldBeInDirectory) } diff --git a/Swift/Tests/PartialIndexTest.swift b/Swift/Tests/PartialIndexTest.swift new file mode 100644 index 000000000..69605ec15 --- /dev/null +++ b/Swift/Tests/PartialIndexTest.swift @@ -0,0 +1,99 @@ +// +// PartialIndexTest.swift +// CouchbaseLite +// +// Copyright (c) 2025 Couchbase, Inc. All rights reserved. +// +// Licensed under the Couchbase License Agreement (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// https://info.couchbase.com/rs/302-GJY-034/images/2017-10-30_License_Agreement.pdf +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +@testable import CouchbaseLiteSwift + +/// Test Spec v1.0.3: +/// https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0007-Partial-Index.md + +class PartialIndexTest: CBLTestCase { + + /// 1. TestCreatePartialValueIndex + /// Description + /// Test that a partial value index is successfully created. + /// Steps + /// 1. Create a partial value index named "numIndex" in the default collection. + /// - expression: "num" + /// - where: "type = 'number'" + /// 2. Check that the index is successfully created. + /// 3. Create a query object with an SQL++ string: + /// - SELECT * + /// FROM _ + /// WHERE type = 'number' AND num > 1000 + /// 4. Get the query plan from the query object and check that the plan contains "USING INDEX numIndex" string. + /// 5. Create a query object with an SQL++ string: + /// - SELECT * + /// FROM _ + /// WHERE type = 'foo' AND num > 1000 + /// 6. Get the query plan from the query object and check that the plan doesn't contain "USING INDEX numIndex" string. + func testCreatePartialValueIndex() throws { + let collection = try db.defaultCollection() + + let config = ValueIndexConfiguration(["num"], where: "type='number'") + try collection.createIndex(withName: "numIndex", config: config) + + var sql = "SELECT * FROM _ WHERE type = 'number' AND num > 1000" + var q = try db.createQuery(sql) + var explain = try q.explain() + XCTAssert(explain.contains("USING INDEX numIndex")) + + sql = "SELECT * FROM _ WHERE type = 'foo' AND num > 1000" + q = try db.createQuery(sql) + explain = try q.explain() + XCTAssertFalse(explain.contains("USING INDEX numIndex")) + } + + /// 2. TestCreatePartialFullTextIndex + /// Description + /// Test that a partial full text index is successfully created. + /// Steps + /// 1. Create following two documents with the following bodies in the default collection. + /// - { "content" : "Couchbase Lite is a database." } + /// - { "content" : "Couchbase Lite is a NoSQL syncable database." } + /// 2. Create a partial value index named "numIndex" in the default collection. + /// - expression: "content" + /// - where: "length(content) > 30" + /// 3. Check that the index is successfully created. + /// 4. Create a query object with an SQL++ string: + /// - SELECT content + /// FROM _ + /// WHERE match(contentIndex, "database") + /// 4. Execute the query and check that: + /// - The query returns the second docume + /// 5. Create a query object with an SQL++ string: + /// - There is one result returned + /// - The returned content is "Couchbase Lite is a NoSQL syncable database." + func testCreatePartialFullTextIndex() throws { + let collection = try db.defaultCollection() + let json1 = "{\"content\":\"Couchbase Lite is a database.\"}" + let json2 = "{\"content\":\"Couchbase Lite is a NoSQL syncable database.\"}" + + try collection.save(document: MutableDocument(json: json1)) + try collection.save(document: MutableDocument(json: json2)) + + let config = FullTextIndexConfiguration(["content"], where: "length(content)>30") + try collection.createIndex(withName: "contentIndex", config: config) + + let sql = "SELECT content FROM _ WHERE match(contentIndex, 'database')" + let q = try db.createQuery(sql) + let result = try q.execute().allResults() + XCTAssertEqual(result.count, 1) + XCTAssertEqual(result[0].toJSON(), json2) + } +} diff --git a/Swift/Tests/VectorSearchTest.swift b/Swift/Tests/VectorSearchTest.swift index 531cf7876..4a8f56df5 100644 --- a/Swift/Tests/VectorSearchTest.swift +++ b/Swift/Tests/VectorSearchTest.swift @@ -1063,14 +1063,14 @@ class VectorSearchTest_Main: VectorSearchTest { let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) try createWordsIndex(config: config) - // Check valid query with 1 and 10000 set limit - for limit in [1, 10000] { + // Check valid query with -1, 0, 1 and 10000 set limit + for limit in [-1, 0, 1, 10000] { _ = try executeWordsQuery(limit: limit) } // Check if error thrown for wrong limit values self.expectError(domain: CBLError.domain, code: CBLError.invalidQuery) { - _ = try self.executeWordsQuery(limit: 10001) + _ = try self.executeWordsQuery(limit: 10001) } } diff --git a/vendor/couchbase-lite-core b/vendor/couchbase-lite-core index 6d3748cd6..71fbec5c8 160000 --- a/vendor/couchbase-lite-core +++ b/vendor/couchbase-lite-core @@ -1 +1 @@ -Subproject commit 6d3748cd6dd8466bd4ac3c07744357662dfc1894 +Subproject commit 71fbec5c8df8255150414b1e2ed7a66ad5a6302c