iOS 앱 로그 수집: Puree에서 수집한 로그를 Cognito를 사용하면서 S3에 업로드

iOS 앱의 로그 수집을 구현해 보았으므로 정리.
타이틀대로, 로그 수집 라이브러리로서 쿡 패드 같은 Puree를 사용,
Cognito에서 AWS를 인증하면서 S3에 로그를 업로드했다.

Puree에 대해서는 아래를 참조하십시오.
Puree 개요
Puree iOS 버전 사용 방법

Cognito에 대해서는 아래를 참조하십시오.
Cognito 개요
Cognito 설정 방법

구현



Objective-C에서 고멘나사이.

라이브러리 도입



CocoaPods에서.
  pod "Puree"
  pod 'AWSiOSSDKv2'
  pod 'AWSCognitoSync'
$ pod install

Cognito의 인증 부분 구현



AWS의 콘솔에서, 정중하게 샘플 코드를 표시해 주는데…



여기에 표시되는 코드를 그대로 복사하면, S3에의 데이터 전송시에 이하와 같은 에러가 나와서 잘 움직이지 않았다.
Upload failed: [Error Domain=com.amazonaws.AWSS3ErrorDomain Code=0 "(null)" UserInfo={HostId=XXXXXXXXXXXX}, Bucket=XXXXXXXXXXXX, Endpoint=XXXXXXXXXXXX.s3.amazonaws.com, Message=The bucket you are attempting to access must be addressed using the specified endpoint. Please send all future requests to this endpoint., Code=PermanentRedirect, RequestId=XXXXXXXXXXXX}]

여러가지 시행착오한 결과, 아무래도 샘플 코드내에서 AWSCognitoCredentialsProvider 의 리전과 AWSServiceConfiguration 의 리전이 엇갈려 있는 것이 원인인 것 같다.AWSCognitoCredentialsProvider 의 리전에 통일하는 것으로 잘 되었다.
(무언가 설정이 이상한지, 샘플 코드가 잘못되어 있는지…?)

이번은 AWSRegionAPNortheast1 이었으므로, 이하와 같이 수정.
- AWSServiceConfiguration *configuration = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionUSEast1 credentialsProvider:credentialsProvider];
+ AWSServiceConfiguration *configuration = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionAPNortheast1 credentialsProvider:credentialsProvider];

S3에 대한 로그 전송 클래스 구현



S3에의 전송에는, AWSS3TransferManager 클래스를 사용한다. 그렇지만 이것, 파일의 업로드 밖에 할 수 없다.

그래서 NSData 객체를 직접 전송 데이터로 지정할 수 있거나 하는 Amazon S3 Transfer Utility
(바이너리를 직접 전송하고 있는 것은 아니고, 뒤로 임시 파일을 작성하고 나서 전송하고 있다.)
그러나 현재 Beta 버전.
사용해 보았을 때, 이전의 Cognito의 에러에 의해 전송 할 수 없었음에도 불구하고, 에러없이 완료되었습니다! 라고 되기 때문에, 조금 배송. 물론 정말로 에러가 없으면 전송 자체는 할 수 있지만.

이번에는 다음 샘플을 참고로 데이터 전송 부분을 구현하면서,
htps : // 기주 b. 코 m / 아 ws ぁ bs / 아 ws-sdk 이오 s mp s s / b b / bs / r / S3T rans f r 마나게 r sa mp / o b c Chi-C / S3T ran s fu r manage r mp ぇ / u p a d d a w w t t r r. m
아래의 Amazon S3 Transfer Utility 의 코드를 참고로, 임시 파일의 생성, 삭제 처리를 붙였다.
htps : // 기주 b. 이 m/아ws/아ws-sdk이오 s/bぉb/마s r/아 Wss3/아Wss3T란s후우루치ぃty. m

그래서 생긴 것이 이쪽.
@interface DataUploader: NSObject

-(id)initWithBucket:(NSString*)bucket;
-(void)uploadWithObjectKey:(NSString*)objectKey dataToUpload:(NSData*)data completion:(void(^)(BOOL))callback;
-(void)cleanUpTemporaryDirectory;

@property (strong, nonatomic) NSString *temporaryDirectoryPath;
@property (strong, nonatomic) NSString *bucketName;
@end
#import <AWSS3/AWSS3.h>

NSString *const DataUploaderIdentifier = @"DataUploaderIdentifier";
NSTimeInterval const TimeoutIntervalForTempLogfile = 60 * 60; // 1hours

@implementation DataUploader

-(id)initWithBucket:(NSString*)bucket
{
    if (self = [super init]) {

        // 一時ファイル用のディレクトリ作成
        _temporaryDirectoryPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[DataUploaderIdentifier aws_md5String]];
        NSURL *directoryURL = [NSURL fileURLWithPath:_temporaryDirectoryPath];
        NSError *error = nil;
        BOOL result = [[NSFileManager defaultManager] createDirectoryAtURL:directoryURL
                                               withIntermediateDirectories:YES
                                                                attributes:nil
                                                                     error:&error];
        if (!result) {
            AWSLogError(@"Failed to create a temporary directory: %@", error);
        }

        // 一時ファイル用ディレクトリから不要なファイルを削除
        __weak DataUploader *weakSelf = self;
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
            [weakSelf cleanUpTemporaryDirectory];
        });

        // バケット名保存
        _bucketName = bucket;
    }
    return self;
}

// S3へのアップロード
-(void)uploadWithObjectKey:(NSString*)objectKey dataToUpload:(NSData*)data completion:(void(^)(BOOL))callback
{
    // 一時ファイル生成
    NSString *fileName = [NSString stringWithFormat:@"%@.tmp", [[NSProcessInfo processInfo] globallyUniqueString]];
    NSString *filePath = [_temporaryDirectoryPath stringByAppendingPathComponent:fileName];
    NSFileManager *fileManager = [NSFileManager defaultManager];
    BOOL result = [fileManager fileExistsAtPath:filePath];
    if(!result){
        NSFileManager *fileManager = [NSFileManager defaultManager];
        result = [fileManager createFileAtPath:filePath contents:[NSData data] attributes:nil];
        if(!result){
            NSLog(@"Failed to create file: %s", strerror(errno));
            callback(NO);
            return;
        }
    }
    NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath];
    if(!fileHandle){
        NSLog(@"Failed to create file handle: %s", strerror(errno));
        callback(NO);
        return;
    }

    [fileHandle writeData:data];
    [fileHandle synchronizeFile];
    [fileHandle closeFile];
    NSURL *url = [NSURL fileURLWithPath:filePath];

    // S3への転送リクエスト生成
    AWSS3TransferManagerUploadRequest *uploadRequest = [AWSS3TransferManagerUploadRequest new];
    uploadRequest.body = url;
    uploadRequest.key = objectKey;
    uploadRequest.bucket = self.bucketName;
    uploadRequest.contentType = @"text/plain";

    // S3への転送
    AWSS3TransferManager *transferManager = [AWSS3TransferManager defaultS3TransferManager];
    [[transferManager upload:uploadRequest] continueWithBlock:^id(AWSTask *task) {
        if (task.error) {
            NSLog(@"Failed to upload log data: %@", task.error);
            callback(NO);
        }
        if (task.result) {
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"Scucess to upload log data!");
                callback(YES);
            });
        }
        return nil;
    }];
}

// 一時ファイルを保存しているディレクトリから不要なファイルを削除
- (void)cleanUpTemporaryDirectory {
    NSError *error = nil;
    NSArray *contentsOfDirectory = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.temporaryDirectoryPath
                                                                                       error:&error];
    if (!contentsOfDirectory) {
        AWSLogError(@"Failed to retrieve the contents of the tempoprary directory: %@", error);
    }

    // 一時ディレクトリのファイルを全てチェック
    __weak DataUploader *weakSelf = self;
    [contentsOfDirectory enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSString *fileName = (NSString *)obj;
        NSString *filePath = [weakSelf.temporaryDirectoryPath stringByAppendingPathComponent:fileName];
        NSError *error = nil;
        NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath
                                                                                    error:&error];
        if (!attributes) {
            AWSLogError(@"Failed to load temporary file attributes: %@", error);
        }
        NSDate *fileCreationDate = [attributes objectForKey:NSFileCreationDate];

        // 作成されてから一時間以上経過しているファイルを削除
        if ([fileCreationDate timeIntervalSince1970] < [[NSDate date] timeIntervalSince1970] - TimeoutIntervalForTempLogfile) {
            BOOL result = [[NSFileManager defaultManager] removeItemAtPath:filePath
                                                                     error:&error];
            if (!result) {
                AWSLogError(@"Failed to remove a temporary file: %@", error);
            }
        }
    }];
}

@end

Amazon S3 Transfer Utility를 사용하는 경우의 코드도 만들었으므로 일단 올려 둔다.uploadWithObjectKey:dataToUpload:completion 를 다음으로 바꾸면 움직일 것.
또 이 경우, 임시 파일 관련의 처리( initWithBucket 의 임시 파일용 디렉토리 작성 처리/파일 삭제 처리, cleanUpTemporaryDirectory )는 Amazon S3 Transfer Utility가 해 주므로 불필요하게 된다.
-(void)uploadWithObjectKey:(NSString*)objectKey dataToUpload:(NSData*)data completion:(void(^)(BOOL))callback
{
    AWSS3TransferUtilityUploadExpression *expression = [AWSS3TransferUtilityUploadExpression new];
    expression.uploadProgress = ^(AWSS3TransferUtilityTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend) {
        dispatch_async(dispatch_get_main_queue(), ^{});
    };

    AWSS3TransferUtilityUploadCompletionHandlerBlock completionHandler = ^(AWSS3TransferUtilityUploadTask *task, NSError *error) {
        dispatch_async(dispatch_get_main_queue(), ^{
            if (error) {
                NSLog(@"Failed to upload log data: %@", error);
                callback(NO);
            }
            else {
                NSLog(@"scucess to upload log data!");
                callback(YES);
            }
        });
    };

    AWSS3TransferUtility *transferUtility = [AWSS3TransferUtility defaultS3TransferUtility];
    [[transferUtility uploadData:data
                          bucket:self.bucketName
                             key:objectKey
                     contentType:@"text/plain"
                      expression:expression
                completionHander:completionHandler] continueWithBlock:^id(AWSTask *task) {
        if (task.error) {
            NSLog(@"Error: %@", task.error);
            callback(NO);
        }
        if (task.exception) {
            NSLog(@"Exception: %@", task.exception);
            callback(NO);
        }

        return nil;
    }];
}

Puree에 도입



Configuration의 설정과 Filter, BufferOutput 플러그인의 구현은 공식 설명 대로 하면 문제 없다고 생각하기 때문에 할애.
여기에서는 위에 쓴 DataUploader 클래스의 사용법만.
// BufferOutputプラグインのクラスにて。
- (void)writeChunk:(PURBufferedOutputChunk *)chunk completion:(void (^)(BOOL))completion
{
    // S3にアップロードするログデータ生成
    NSData *logData = 【ログデータ生成処理】;
    // S3でのファイルのキーを生成
    NSString *objectKey = 【オブジェクトキー】;

    // 送信
    DataUploader *dataUploader = [[DataUploader alloc] initWithBucket:【バケット名】];
    [dataUploader uploadWithObjectKey:objectKey dataToUpload:logs completion:^(BOOL result) { completion(result); }];
}

좋은 웹페이지 즐겨찾기