ユーザーのログを集めたい
iOSのクライアントの動きをログとして集積したいので、その方法としてAWSのCognitoでTemporary Credentialsを各クライアントに発行して、Kinesis Firehoseに集めたいデータを渡してS3に保存することにしました。
Kinesis Firehose(以下Firehose)を使うとKinesis Stream(従来のKinesis: 以下Kinesis)と異なり、いきなりS3やRed Shiftにデータを保存できるのが特徴です。
参考にしたサイト
-
AWSのiOS developer guide
これはKinesisとFirehoseのふたつに関して実装手順が書いてありますがObjective-Cで記述されていました。Objective-Cには馴染みがあまりなかったのでこれだけでは実装完了できませんでした。 -
classmethodさんの記事
コンソール画面での手順も書いてあり、これに沿っていけばKinesisの導入まではすぐいけそうです。ただし、コードはSwift3で書かれていないので自分で修正しないといけない点がありました。
ハマったところ
1, ユーザーロールに紐づく権限周り(IAM)
1, Cognitoの権限
Cognitoにはデフォルトの権限に加えてFirehoseにアクセスする権限(最低でもPutRecord
, PutRecordBatch
)を付与します。CognitoのFederated IdentitiesのほうでEdit Identity Poolを選択して権限を確かめます。

Authenticated RoleとUnauthenticated Roleに設定している権限を見ると、以下のようなポリシーがデフォルトで入っていました。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"mobileanalytics:PutEvents",
"cognito-sync:*",
"cognito-identity:*"
],
"Resource": [
"*"
]
}
]
}
これにIAM画面で以下のようなポリシーがあるので追加しました。
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"firehose:*"
],
"Effect": "Allow",
"Resource": "*"
}
]
}
2, Firehoseの権限
これはデフォルトで準備されるやつに
{
"Effect": "Allow",
"Action": [
"firehose:PutRecord",
"firehose:PutRecordBatch"
],
"Resource": [
"arn:aws:firehose:us-east-1:account-id:deliverystream/delivery-stream-name"
]
}
を付け加えました。
IAM Simulatorで確かめながらやると確実です。
2, iOSのsubmitAllRecords()
のところ
Firehoseに関する記事は少なくてSwift3ではどうすればいいのかわかりにくかったです。
こんな感じで実装してうまくいきました。実際はこれらをシングルトンにまとめました。
import AWSCore
import AWSCognito
import AWSKinesis
////
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
////
configureAWSCognito()
////
return true
}
func configureAWSCognito() {
// Settings for AWSCognito
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .usEast1, identityPoolId: "us-east-1:00000000-xxxx-0000-0000-xxxxxxxxxxxx")
let configuration = AWSServiceConfiguration(region:.usEast1, credentialsProvider:credentialsProvider)
AWSFirehoseRecorder.register(with: configuration, forKey: "FirehoseName")
AWSServiceManager.default().defaultServiceConfiguration = configuration
updateIdentifier()
}
func updateIdentifier() {
// Settings for AWSCognito
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .usEast1, identityPoolId: "us-east-1:00000000-xxxx-0000-0000-xxxxxxxxxxxx")
credentialsProvider.getIdentityId().continue({ (task: AWSTask!) -> Any! in
if (task.error != nil) {
print("Error: " + task.error!.localizedDescription)
}
else {
// the task result will contain the identity id
self.cognitoId = task.result as? String
// print("CognitoId: ")
// print(task.result as? String)
}
return nil
})
}
func record(data: [Data]) {
guard let firehoseRecorder = AWSFirehoseRecorder.default() else { return }
let tasks: [AWSTask] = data.map { firehoseRecorder.saveRecord($0, streamName: "FirehoseName") }
let task = AWSTask(forCompletionOfAllTasks: tasks).continue(successBlock: { (task: AWSTask<AnyObject>) in
return firehoseRecorder.submitAllRecords()
})
task.continue({ [weak self] (task: AWSTask<AnyObject>) in
switch task.error {
case let .some(error):
print("Error: \(error)")
case .none:
print("FirehoseRecorded")
}
return nil
})
}
特にハマったのは参考にしていたページの
AWSTask(forCompletionOfAllTasks: tasks).continueWithSuccessBlock { task -> AnyObject? in
recorder.submitAllRecords()
}.continueWithBlock { task -> AnyObject? in
if let err = task.error {
NSLog("Error: [%@]", err)
}
return nil
}
の部分を
let task = AWSTask(forCompletionOfAllTasks: tasks).continue(successBlock: { (task: AWSTask<AnyObject>) in
return firehoseRecorder.submitAllRecords()
})
task.continue({ [weak self] (task: AWSTask<AnyObject>) in
switch task.error {
case let .some(error):
print("Error: \(error)")
case .none:
print("FirehoseRecorded")
}
return nil
})
とcontinueを分割した点です。
これでS3に欲しいログをためられるようになりました。