Add Extract-Based Log Searching

Summary: Some log readers care about the substring rather than the entire line, so fetching an entire line is optional.

Reviewed By: nqmtuan, marekcirkos

Differential Revision: D3035471

fb-gh-sync-id: d7727817f650a4c87dbfff59be6b7fe6136693de
shipit-source-id: d7727817f650a4c87dbfff59be6b7fe6136693de
This commit is contained in:
Lawrence Lomax 2016-03-11 02:24:59 -08:00 коммит произвёл Facebook Github Bot 6
Родитель 09c348945f
Коммит fa3c2bc959
3 изменённых файлов: 128 добавлений и 84 удалений

Просмотреть файл

@ -52,14 +52,15 @@
/**
Constructs a Batch Log Search for the provided mapping of log names to predicates.
The provided mapping is an NSDictionary where:
- The keys are an NSArray of NSStrings of the names of the Logs to search. An Empty list means that the value will apply to all predicates.
- The values are an NSArray of FBLogSearchPredicates of the predicates to search the named logs with.
- The keys are the names of the Diagnostics to search. The empty string matches against all input diagnostics.
- The values are an NSArray of FBLogSearchPredicates of the predicates to search the the diagnostic with.
@param mapping the mapping to search with.
@param lines YES to include the full line in the output, NO for the matched substring.
@param error an error out for any error in the mapping format.
@return an FBBatchLogSearch instance if the mapping is valid, nil otherwise.
*/
+ (instancetype)withMapping:(NSDictionary *)mapping error:(NSError **)error;
+ (instancetype)withMapping:(NSDictionary *)mapping lines:(BOOL)lines error:(NSError **)error;
/**
Runs the Reciever over an array of Diagnostics.
@ -77,9 +78,10 @@
@param diagnostics an NSArray of FBDiagnostics to search.
@param predicate a Log Search Predicate to search with.
@param lines YES to include the full line in the output, NO for the matched substring.
@return an NSDictionary mapping log names to the matching lines that were found in the diagnostics.
*/
+ (NSDictionary *)searchDiagnostics:(NSArray *)diagnostics withPredicate:(FBLogSearchPredicate *)predicate;
+ (NSDictionary *)searchDiagnostics:(NSArray *)diagnostics withPredicate:(FBLogSearchPredicate *)predicate lines:(BOOL)lines;
@end

Просмотреть файл

@ -45,7 +45,7 @@
}
NSRange range = line.length ? NSMakeRange(0, line.length - 1) : NSMakeRange(0, 0);
NSTextCheckingResult *result = [self.regularExpression firstMatchInString:line options:0 range:range];
if (result.range.location == NSNotFound) {
if (!result || result.range.location == NSNotFound || result.range.length < 1) {
return nil;
}
return [line substringWithRange:result.range];
@ -215,7 +215,7 @@
{
NSRegularExpression *regex = [NSRegularExpression
regularExpressionWithPattern:pattern
options:0
options:NSRegularExpressionAnchorsMatchLines
error:nil];
return [[FBLogSearchPredicate_Regex alloc] initWithRegularExpression:regex];
}
@ -299,6 +299,7 @@
@interface FBBatchLogSearch ()
@property (nonatomic, copy, readonly) NSDictionary *mapping;
@property (nonatomic, assign, readonly) BOOL lines;
@end
@ -306,25 +307,18 @@
#pragma mark Initializers
+ (instancetype)withMapping:(NSDictionary *)mapping error:(NSError **)error
+ (instancetype)withMapping:(NSDictionary *)mapping lines:(BOOL)lines error:(NSError **)error
{
for (id key in mapping.allKeys) {
if (![key isKindOfClass:NSArray.class]) {
return [[FBControlCoreError describeFormat:@"%@ key is not an array", key] fail:error];
}
if (![FBCollectionInformation isArrayHeterogeneous:key withClass:NSString.class]) {
return [[FBControlCoreError describeFormat:@"%@ key is not an array of strings", key] fail:error];
}
if (![FBCollectionInformation isDictionaryHeterogeneous:mapping keyClass:NSString.class valueClass:NSArray.class]) {
return [[FBControlCoreError describeFormat:@"%@ is not an dictionary<string, string>", mapping] fail:error];
}
for (id value in mapping.allValues) {
if (![value isKindOfClass:NSArray.class]) {
return [[FBControlCoreError describeFormat:@"%@ value is not an array", value] fail:error];
}
if (![FBCollectionInformation isArrayHeterogeneous:value withClass:FBLogSearchPredicate.class]) {
return [[FBControlCoreError describeFormat:@"%@ value is not an array of log search predicates", value] fail:error];
}
}
return [[FBBatchLogSearch alloc] initWithMapping:mapping];
return [[FBBatchLogSearch alloc] initWithMapping:mapping lines:lines];
}
+ (instancetype)inflateFromJSON:(NSDictionary *)json error:(NSError **)error
@ -332,14 +326,20 @@
if (![json isKindOfClass:NSDictionary.class]) {
return [[FBControlCoreError describeFormat:@"%@ is not a dictionary", json] fail:error];
}
if (![FBCollectionInformation isDictionaryHeterogeneous:json keyClass:NSArray.class valueClass:NSArray.class]) {
return [[FBControlCoreError describeFormat:@"%@ is not a dictionary of <array, array>", json] fail:error];
NSNumber *lines = json[@"lines"];
if (![lines isKindOfClass:NSNumber.class]) {
return [[FBControlCoreError describeFormat:@"%@ is not a number for 'lines'", lines] fail:error];
}
NSMutableDictionary *mapping = [NSMutableDictionary dictionary];
for (NSString *key in json.allKeys) {
NSDictionary *jsonMapping = json[@"mapping"];
if (![FBCollectionInformation isDictionaryHeterogeneous:jsonMapping keyClass:NSString.class valueClass:NSArray.class]) {
return [[FBControlCoreError describeFormat:@"%@ is not a dictionary of <array, array>", jsonMapping] fail:error];
}
NSMutableDictionary *predicateMapping = [NSMutableDictionary dictionary];
for (NSString *key in jsonMapping.allKeys) {
NSMutableArray *predicates = [NSMutableArray array];
for (NSDictionary *predicateJSON in json[key]) {
for (NSDictionary *predicateJSON in jsonMapping[key]) {
FBLogSearchPredicate *predicate = [FBLogSearchPredicate inflateFromJSON:predicateJSON error:error];
if (!predicate) {
return [[FBControlCoreError describeFormat:@"%@ is not a predicate", predicateJSON] fail:error];
@ -347,12 +347,12 @@
[predicates addObject:predicate];
}
mapping[key] = [predicates copy];
predicateMapping[key] = [predicates copy];
}
return [self withMapping:[mapping copy] error:error];
return [self withMapping:[predicateMapping copy] lines:lines.boolValue error:error];
}
- (instancetype)initWithMapping:(NSDictionary *)mapping
- (instancetype)initWithMapping:(NSDictionary *)mapping lines:(BOOL)lines
{
self = [super init];
if (!self) {
@ -360,6 +360,7 @@
}
_mapping = mapping;
_lines = lines;
return self;
}
@ -374,6 +375,7 @@
}
_mapping = [coder decodeObjectForKey:NSStringFromSelector(@selector(mapping))];
_lines = [coder decodeBoolForKey:NSStringFromSelector(@selector(lines))];
return self;
}
@ -381,24 +383,28 @@
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:self.mapping forKey:NSStringFromSelector(@selector(mapping))];
[coder encodeBool:self.lines forKey:NSStringFromSelector(@selector(lines))];
}
#pragma mark NSCopying
- (instancetype)copyWithZone:(NSZone *)zone
{
return [[FBBatchLogSearch alloc] initWithMapping:self.mapping];
return [[FBBatchLogSearch alloc] initWithMapping:self.mapping lines:self.lines];
}
#pragma mark FBJSONSerializationDescribeable Implementation
- (id)jsonSerializableRepresentation
{
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
NSMutableDictionary *mappingDictionary = [NSMutableDictionary dictionary];
for (NSArray *key in self.mapping) {
dictionary[key] = [self.mapping[key] valueForKey:@"jsonSerializableRepresentation"];
mappingDictionary[key] = [self.mapping[key] valueForKey:@"jsonSerializableRepresentation"];
}
return [dictionary copy];
return @{
@"lines" : @(self.lines),
@"mapping" : [mappingDictionary copy],
};
}
#pragma mark FBDebugDescribeable Implementation
@ -429,12 +435,12 @@
return NO;
}
return [self.mapping isEqualToDictionary:object.mapping];
return self.lines == object.lines && [self.mapping isEqualToDictionary:object.mapping];
}
- (NSUInteger)hash
{
return self.mapping.hash;
return (NSUInteger) self.lines ^ self.mapping.hash;
}
#pragma mark Public API
@ -448,36 +454,35 @@
// Construct and NSArray<FBLogSearch> instances
NSMutableArray *searchers = [NSMutableArray array];
for (NSArray *nameArray in self.mapping.allKeys) {
NSArray *predicates = self.mapping[nameArray];
for (NSString *diagnosticName in self.mapping.allKeys) {
NSArray *predicates = self.mapping[diagnosticName];
if ([nameArray isEqualToArray:@[]]) {
if ([diagnosticName isEqualToString:@""]) {
for (FBDiagnostic *diagnostic in diagnostics) {
for (FBLogSearchPredicate *predicate in predicates) {
[searchers addObject:[FBLogSearch withDiagnostic:diagnostic predicate:predicate]];
}
}
}
for (NSString *name in nameArray) {
FBDiagnostic *diagnostic = namesToDiagnostics[name];
if (!diagnostic) {
continue;
}
for (FBLogSearchPredicate *predicate in predicates) {
[searchers addObject:[FBLogSearch withDiagnostic:diagnostic predicate:predicate]];
}
FBDiagnostic *diagnostic = namesToDiagnostics[diagnosticName];
if (!diagnostic) {
continue;
}
for (FBLogSearchPredicate *predicate in predicates) {
[searchers addObject:[FBLogSearch withDiagnostic:diagnostic predicate:predicate]];
}
}
// Perform the search, concurrently
BOOL lines = self.lines;
NSArray *results = [FBConcurrentCollectionOperations
mapFilter:[searchers copy]
map:^ NSArray * (FBLogSearch *searcher) {
NSString *line = searcher.firstMatchingLine;
if (!line) {
NSString *match = lines ? searcher.firstMatchingLine : searcher.firstMatch;
if (!match) {
return nil;
}
return @[searcher.diagnostic.shortName, line];
return @[searcher.diagnostic.shortName, match];
}
predicate:FBConcurrentCollectionOperations.notNullPredicate];
@ -497,9 +502,9 @@
return [output copy];
}
+ (NSDictionary *)searchDiagnostics:(NSArray *)diagnostics withPredicate:(FBLogSearchPredicate *)predicate
+ (NSDictionary *)searchDiagnostics:(NSArray *)diagnostics withPredicate:(FBLogSearchPredicate *)predicate lines:(BOOL)lines
{
return [[self withMapping:@{@[] : @[predicate]} error:nil] search:diagnostics];
return [[self withMapping:@{@[] : @[predicate]} lines:lines error:nil] search:diagnostics];
}
@end

Просмотреть файл

@ -59,7 +59,7 @@
FBLogSearch *searcher = [FBLogSearch withDiagnostic:self.simulatorSystemLog predicate:[FBLogSearchPredicate regex:
@"layer position \\d+ \\d+ bounds \\d+ \\d+ \\d+ \\d+"
]];
XCTAssertEqualObjects(searcher.firstMatch, @"layer position 375 667 bounds 0 0 750 1334");
XCTAssertEqualObjects(searcher.firstMatch, @"layer position 375 667 bounds 0 0 750 133");
XCTAssertEqualObjects(searcher.firstMatchingLine, @"Mar 7 16:50:18 some-hostname backboardd[24912]: layer position 375 667 bounds 0 0 750 1334");
}
@ -85,62 +85,99 @@
@end
@interface FBBatchLogSearcherTests : XCTestCase
@interface FBBatchLogSearcherTests : FBControlCoreValueTestCase
@end
@implementation FBBatchLogSearcherTests
- (void)testBatchSearchFindsAcrossMultipleDiagnostics
- (NSDictionary *)complexMapping
{
NSDictionary *mapping = @{
@[@"simulator_system", @"tree"] : @[
[FBLogSearchPredicate substrings:@[@"Springboard", @"IOHIDSession", @"rect"]],
[FBLogSearchPredicate regex:@"layer position \\d+ \\d+ bounds \\d+ \\d+ \\d+ \\d+"]
],
@[@"simulator_system"] : @[
return @{
@"simulator_system" : @[
[FBLogSearchPredicate substrings:@[@"Springboard", @"SpringBoard", @"IOHIDSession"]],
[FBLogSearchPredicate regex:@"layer position \\d+ \\d+ bounds \\d+ \\d+ \\d+ \\d+"],
[FBLogSearchPredicate substrings:@[@"ADDING REMOTE com.apple.Maps"]],
],
@[@"tree"] : @[
[FBLogSearchPredicate regex:@"(ANIMPOSSIBLE|REGEAAAAAAAAA)"],
],
@[@"photo0"] : @[
[FBLogSearchPredicate substrings:@[@"111", @"222"]],
@"tree" : @[
[FBLogSearchPredicate substrings:@[@"Springboard", @"SpringBoard", @"IOHIDSession"]],
[FBLogSearchPredicate regex:@"(ANIMPOSSIBLE|REGEAAAAAAAAA)"],
],
@"photo0" : @[
[FBLogSearchPredicate substrings:@[@"BAAAAAAAA"]],
]
};
NSError *error = nil;
FBBatchLogSearch *batchSearch = [FBBatchLogSearch withMapping:mapping error:&error];
XCTAssertNil(error);
XCTAssertNotNil(batchSearch);
NSDictionary *results = [batchSearch search:@[
self.simulatorSystemLog,
self.treeJSONDiagnostic,
self.photoDiagnostic
]];
XCTAssertNotNil(results);
XCTAssertEqual([results[@"simulator_system"] count], 3u);
XCTAssertEqual([results[@"tree"] count], 1u);
XCTAssertEqual([results[@"photo0"] count], 0u);
}
- (void)testSearchAllFindsAcrossAllDiagnostics
- (NSDictionary *)searchAllMapping
{
NSDictionary *mapping = @{@[] : @[
[FBLogSearchPredicate substrings:@[@"Springboard", @"IOHIDSession", @"rect"]],
return @{@"" : @[
[FBLogSearchPredicate substrings:@[@"Springboard", @"SpringBoard", @"IOHIDSession"]],
[FBLogSearchPredicate regex:@"layer position \\d+ \\d+ bounds \\d+ \\d+ \\d+ \\d+"],
[FBLogSearchPredicate substrings:@[@"ADDING REMOTE com.apple.Maps"]],
[FBLogSearchPredicate regex:@"(ANIMPOSSIBLE|REGEAAAAAAAAA)"],
[FBLogSearchPredicate substrings:@[@"111", @"222"]],
]};
NSError *error = nil;
FBBatchLogSearch *batchSearch = [FBBatchLogSearch withMapping:mapping error:&error];
XCTAssertNil(error);
XCTAssertNotNil(batchSearch);
NSDictionary *results = [batchSearch search:@[
}
- (NSArray *)diagnostics
{
return @[
self.simulatorSystemLog,
self.treeJSONDiagnostic,
self.photoDiagnostic
]];
self.photoDiagnostic,
];
}
- (void)testValueSemantics
{
NSArray *batches = @[
[FBBatchLogSearch withMapping:self.complexMapping lines:YES error:nil],
[FBBatchLogSearch withMapping:self.complexMapping lines:NO error:nil],
[FBBatchLogSearch withMapping:self.searchAllMapping lines:YES error:nil],
[FBBatchLogSearch withMapping:self.searchAllMapping lines:NO error:nil],
];
[self assertEqualityOfCopy:batches];
[self assertUnarchiving:batches];
[self assertJSONSerialization:batches];
[self assertJSONDeserialization:batches];
}
- (void)testBatchSearchFindsLinesAcrossMultipleDiagnostics
{
FBBatchLogSearch *batchSearch = [FBBatchLogSearch withMapping:self.complexMapping lines:YES error:nil];
NSDictionary *results = [batchSearch search:self.diagnostics];
XCTAssertNotNil(results);
XCTAssertEqual([results[@"simulator_system"] count], 3u);
XCTAssertEqual([results[@"tree"] count], 1u);
XCTAssertEqual([results[@"photo0"] count], 0u);
XCTAssertEqualObjects(results[@"simulator_system"][0], @"Mar 7 16:50:18 some-hostname backboardd[24912]: ____IOHIDSessionScheduleAsync_block_invoke: thread_id=0x700000323000");
XCTAssertEqualObjects(results[@"simulator_system"][1], @"Mar 7 16:50:18 some-hostname backboardd[24912]: layer position 375 667 bounds 0 0 750 1334");
XCTAssertEqualObjects(results[@"simulator_system"][2], @"Mar 7 16:50:21 some-hostname SpringBoard[24911]: ADDING REMOTE com.apple.Maps, <BBRemoteDataProvider 0x7fca290e3fc0; com.apple.Maps>");
}
- (void)testBatchSearchFindsExtractsAcrossMultipleDiagnostics
{
FBBatchLogSearch *batchSearch = [FBBatchLogSearch withMapping:self.complexMapping lines:NO error:nil];
NSDictionary *results = [batchSearch search:self.diagnostics];
XCTAssertNotNil(results);
XCTAssertEqual([results[@"simulator_system"] count], 3u);
XCTAssertEqual([results[@"tree"] count], 1u);
XCTAssertEqual([results[@"photo0"] count], 0u);
XCTAssertEqualObjects(results[@"simulator_system"][0], @"IOHIDSession");
XCTAssertEqualObjects(results[@"simulator_system"][1], @"layer position 375 667 bounds 0 0 750 133");
XCTAssertEqualObjects(results[@"simulator_system"][2], @"ADDING REMOTE com.apple.Maps");
}
- (void)testSearchAllFindsAcrossAllDiagnostics
{
FBBatchLogSearch *batchSearch = [FBBatchLogSearch withMapping:self.searchAllMapping lines:YES error:nil];
NSDictionary *results = [batchSearch search:self.diagnostics];
XCTAssertNotNil(results);
XCTAssertEqual([results[@"simulator_system"] count], 4u);
XCTAssertEqual([results[@"tree"] count], 1u);