- 投稿日:2020-06-02T23:22:38+09:00
URLsessionのURLComponentsをgurad letでアンラップする方法
はじめに
URLComponentsの使い方は他の記事でも紹介されていますが、基本的に暗黙的アンラップ(!)を使用しています。
(参考:どこよりも分かりやすいSwiftの"?"と"!")
もしURLにnilが入るとアプリがクラッシュしてしまうのでguradで安全にアンラップする方法を紹介します。URLsessionの使い方は下記の記事を参考にしてみてください。
(参考URLsessionを用いたHTTPリクエストの方法)guradでアンラップする方法
まず暗黙的アンラップを用いている方法です。(良くないパターン)
var urlComponents = URLComponents(string: "https://hogehoge.hoge")! // "!"を使用 urlComponents.queryItems = [ URLQueryItem(name: "email", value: "hoge@hoge.com"), URLQueryItem(name: "username", value: "hogehoge") ] var request = URLRequest(url: urlComponents.url!) // "!"を使用続いてguradで安全にアンラップした場合です。
guard var components = URLComponents(string: "https://hogehoge.hoge") else { return nil } // guardでアンラップ。returnでnilを返す。 urlComponents.queryItems = [ URLQueryItem(name: "email", value: "hoge@hoge.com"), URLQueryItem(name: "username", value: "hogehoge") ] guard let url = urlComponents.url else { return nil } // 一行追加してguradでアンラップ。同じくreturnでnilを返す。 var request = URLRequest(url: url) // urlを安全にアンラップできる。おわりに
URLsessionに限らず、アプリがクラッシュしないように基本的には"!"を使わずにguradなどでアンラップします。
しかし、returnで何を返すかが変わったり、そもそもguardを使わなかったりと型によっても変わってくるので、今回はURLComponentsについて紹介しました。
- 投稿日:2020-06-02T19:27:25+09:00
RealityKitで直方体の6面それぞれに異なるテクスチャを貼る方法
はじめに
先月、「iPad Pro の LiDARスキャナ を試してみた」という記事を書きました。
その時はじめて RealityKit をいじったのですが、その後、ちょっとしたアイディアを実装してみようと思った時、RealityKit上のオブジェクトにコードのみでテクスチャを貼り付ける方法が見つからず、意外と悩んでしまいました。ようやくその方法がわかったので、備忘録を兼ねて記事とします。
2017年に「 ARKitで立方体の6面それぞれに異なるテクスチャを貼る方法」という記事を書いていますが、つまり同じことを RealityKit でやってみようという話です。
環境環境等
- MacBook (Retina, 12inch, Early 2015)
- macOS Catalina Version 10.15.5
- Xcode Version 11.5 (11E608c)
- iPad Pro (12.9インチ) (第4世代)/ iPhone X
- iOS 13.5 (17F75)
本記事に記載した範囲では LiDAR 搭載である必要はありませんので、普通の iPhone で試すことができます。
実装
プロジェクトの作成
Xcodeで、テンプレートとして “Augmented Realit App” を選択し、Content Technology には “Reality Kit” を選択してプロジェクトを作成します。
テクスチャの準備
「ARKitで立方体の6面それぞれに異なるテクスチャを貼る方法」同様、6面分の画像を用意します。
画像は Assets に Texture として登録しておきます。
具体的には、Assets.xcassets の "+" ボタンをクリックし、"New Texture Set" を選択し、現れる枠に画像をドラッグ&ドロップします。名前も適当につけておきます。コード
まずは、ViewController.swift の viewDidLoad() に下記を追加します。
シーンにアンカーを追加し、直方体の生成と配置を行なってます。なお、雛形のままだと、"Experience.rcproject"の読み込みとシーンへの追加なども viewDidLoad() 内に書かれていますが、それは削除してかまいません。
ViewController.swift// ----- ARKit3.5 ScenUnderstanding設定 // いったん空に arView.environment.sceneUnderstanding.options = [] // オクルージョンを有効化 arView.environment.sceneUnderstanding.options.insert(.occlusion) // デバッグオプションの設定 arView.debugOptions.insert(.showWorldOrigin) arView.debugOptions.insert(.showStatistics) // シーンにアンカーを追加する let anchor = AnchorEntity(world: SIMD3<Float>(0.0, 0.0, 0.0)) // 原点=デバイスの位置に置く arView.scene.anchors.append(anchor) // 直方体を生成 let boxEntity = makeBox() // 初期位置の指定: anchorに対して、50cm画面奥、10cm上方に配置 boxEntity.position = SIMD3<Float>(0.0, 0.1, -0.5) anchor.addChild(boxEntity)直方体の生成〜テクスチャの貼り込みは下記のように行います。
ViewController.swiftfunc makeBox() -> ModelEntity { // 幅10cm、高さ20cm、奥行き30cmの立方体を作成 let boxModel = ModelEntity(mesh: .generateBox(width: 0.1, height: 0.2, depth: 0.3, cornerRadius: 0.01, splitFaces: true)) // 6面、別々のテクスチャを貼る var material1 = SimpleMaterial() var material2 = SimpleMaterial() var material3 = SimpleMaterial() var material4 = SimpleMaterial() var material5 = SimpleMaterial() var material6 = SimpleMaterial() material1.baseColor = try! .texture(.load(named: "Texture1")) material2.baseColor = try! .texture(.load(named: "Texture2")) material3.baseColor = try! .texture(.load(named: "Texture3")) material4.baseColor = try! .texture(.load(named: "Texture4")) material5.baseColor = try! .texture(.load(named: "Texture5")) material6.baseColor = try! .texture(.load(named: "Texture6")) boxModel.model?.materials = [material1, material2, material3, material4, material5, material6] return boxModel }ポイントは下記の2つ。
6面に別々のテクスチャを貼る
.generateBox() で直方体の形状を定義していますが、この splitFaces: を true とすると、6面別々のテクスチャが貼られます。逆に false だと、どの面にも1番目のテクスチャが貼られます。
なお、この例では立方体の角を丸めていますが、cornerRadius: を 0 にすれば、普通の角ばった直方体になります。
画像ファイルからテクスチャを読み込む
SimpleMaterial() で、Material を生成し、その色(baseColor)として Assets から読み込んだテクスチャを設定します。
むむ、ここが分かりにくかったところです!
baseColor という属性名からは、赤とか青といった"色"を設定する属性としか想像できませんが、ここにテクスチャを貼れるという点が想像の範囲外。
またそこにテクスチャをセットする際に、引数を取る列挙型を利用するという点もやや技巧的なように感じます(よく考えると合理的なのですが)。実行結果
まずは、スクリーンショットをご覧ください。
赤がX軸、緑がY軸、青がZ軸で、それらが交差している原点が、アプリを立ち上げたときのデバイスの初期位置で、一歩下がって、Z軸プラス方向からやや見下ろしている画像です。
こちらは反対側から、つまりZ軸マイナス方向から原点方向をやや仰ぎ見る形になっている画像です。
ちょっと予想外の結果です。
「 ARKitで立方体の6面それぞれに異なるテクスチャを貼る方法」の実行結果と比較してその違いを確認してください。
なんとテクスチャが裏返になっているうえに上下が逆で、各面に割り付ける順番も異なっています。SceneKitでのテクスチャ貼り付けとこんなに違う結果が得られるなんて。こうならない設定があるか、あるいは何か合理的な理由があってこうなっているのだと思いますが、そのあたりはまだ調べがついていません。
おわりに
RealityKit を使用して、コードだけでAR空間にオブジェクトを生成し、そこにテクスチャを貼り付けてみました。結果的に SceneKit との意外な差異に気がつくことにもなりました。
RealityKit を使うと簡単にARアプリを作成することができる反面、パーティクルが使えないなど、SceneKit に比べまだまだ不足している機能が多くあります。このあたりは今後拡充していくものと期待しています(とりあえず直近のWWWDCでの発表を楽しみにしています。)。
本稿がARアプリ開発の参考になれば幸いです。
参考
- RealityKit + ARKit 3 + SwiftUI で宙に浮く Hello World テキスト in 拡張現実
- ios - RealityKit / ARViewの両面素材を指定する方法は?
- RealityKit の説明と SceneKit との違いについて考える (Xcode 11.0 Beta 1)
- ARKitで立方体の6面それぞれに異なるテクスチャを貼る方法
- iPad Pro の LiDARスキャナ を試してみた
個人的な宣伝
先月、iOS(iPad)アプリ「簡単
iOS(iPadOS)アプリ「簡単便利な階層型情報メモアプリ HiMemo」を個人開発でリリースしました。リリース時に悩んだセキュリティポリシーについての記事を近日中にまとめたいと思っています。
ご興味のある方は、こちらをご覧ください → https://apps.apple.com/jp/app/himemo/id1506694081
※ARは関係ないアプリです。
- 投稿日:2020-06-02T19:07:44+09:00
【Swift】UIRefreshControlを使ってTableViewを下に引っ張ったときに読み込まれるやつを作りたい
TableViewを下に引っ張ったときに読み込まれるやつ
twitterとかにあるやつのこと!
tableviewを下に引っ張るとクルクルが出てきて再度読み込まれるっていうアレです
qiitaとかネットでいろいろ調べたんだけど、それだとちゃんと動かなかったのでここに書いときます
この記事ではTableViewで使う時のことを書きますが、iOS10からスクロールするViewならなんでもつけることができるようになったみたいです!TableViewにUIRefreshControlを追加して、AddTargetする
tableView.refreshControl = UIRefreshControl() tableView.refreshControl?.addTarget(self, action: #selector(refresh), for: .valueChanged)これでもうTableViewにくるくるが追加されました
くるくるした時の処理を書く
@objc func refresh() { /* ここに処理を書く */ tableView.refreshControl?.endRefreshing() //これを呼び出すとくるくるが止まる }refreshControl.endRefreshing()を呼んであげないと、永遠にくるくるし続けるので注意!
呼び出すタイミングは処理が終わったタイミングがいいですね!DispatchQueueとか
Timer.scheduledTimerとかを使ってあげるのがいいと思う
よきくるくるライフを!
結構簡単に実装できて嬉しいですね!
環境
Xcode 11.5
参考
https://developer.apple.com/documentation/uikit/uirefreshcontrol
- 投稿日:2020-06-02T19:00:38+09:00
個人開発したアプリが1万ダウンロード突破したので振り返ってみる
開発したアプリ
Shiori web for safari
コンセプト
『webにしおりをはさめるアプリ』というコンセプトで開発した。
機能
1. 記事保存機能
読んでいる最中の記事やwebサイトをスクロール位置とともに保存できる。それによって、次回開いたときに読んでいた場所からすぐに再開することができる。どこまで読んだか、とかいちいちスクロールして探す必要がない。
2. 動画保存機能
觀ている最中の動画を再生位置とともに保存できる。それによって、次回開いたときに觀ていた時間から再開することができる。簡単に共有もできる。動画の面白い部分を何度も觀たり、友達に見せることがワンタップで可能になる。
使い方
safariで任意のページを開き、共有 > Shioriをタップ。
経緯
今まで作ったアプリはポートフォリオ用とかでユーザーがいなかったので、実際に需要がありそうなアプリを作りたかった
iOSアプリ開発の勉強
という主に2つの理由で開発した。
実装
期間
- 実装期間はだいたい一週間くらい(初版をリリースするまで)。
技術
- swift, Xcode
- iosで完結している。share extensionで記事のデータを取得し、coreDataに保存している。
- 分析はfirebase
こだわり
このアプリではiosで提供されているのshare extensionという機能を利用して記事を保存している。
だが標準では記事のタイトルやURLしか取得できないので、共有ボタンを押したときにjavascriptファイルを実行して、スクロール位置や動画の再生位置を保存している。この機能を使っている先人がほぼいなくて苦労した。
この機能についてはqiitaにも書いた。
データ
ダウンロード数は現状12650くらい(20.05.27現在)
広告を一応貼ってはいるが、広告は自由にオフにできるのと、アプリの性質上ユーザーの一回あたりの利用時間が短いのもあって、広告収入は月に数百円くらい。
日本appstoreにおけるニュースカテゴリで1位を達成(2020.03.17)
中国appstoreにおけるニュースカテゴリで118位達成(2020.03.06)
韓国appstoreにおけるニュースカテゴリで4位達成
その他、台湾・香港等においてもランキング入りリリース後
リリース後しばらくはいわゆるゾンビアプリだった
→ダウンロード数50、とか。ユーザーも自分を含めて2~3人、という状況が数ヶ月続いた。
しばらくしてwechatのおすすめかなんかに乗った(たぶん)
→中国圏からのダウンロードが爆伸びした
その後アプリ紹介系のサイトに何度か紹介してもらって、増えていった。
学び
9割のアプリはゾンビアプリ
- ゾンビアプリというのは、検索順位が低すぎて誰にも認知されないアプリのこと。appstore等で自分たちが見聞きしているアプリは上位1%の勝ち組アプリ。実際自分で作っても基本見向きもされない。
これは反省も込めてだが、アプリ名とスクリーンショットですべての情報を伝えるべき。それ以外の情報は伝わらないと思って良い
アプリ出すなら多言語対応して損はない
- アプリの種類によっては不可能なものもあるだろうが、可能ならば複数の言語に対応しておくと良いんじゃないかと思う。どの国で使われるかはリリースしてみないとわからない。ちなみにこのアプリも中国と韓国のユーザーさんが大半を占めている。
多言語対応するなら最初からしておいたほうがいい
- 前のとの関連で、リリースした後に新しく外国語に対応しようとすると、スクリーンショットを作り直したり専用のファイルを作成しないといけなくて意外と大変。
完成からリリースまで時間かかる
- Appleの審査は厳しくて、謎の理由でリジェクトされることが結構ある。
- このアプリもなかなか使い方を理解してもらえなくて、提出からリリースまで一ヶ月くらいかかった。
人に使ってもらえるアプリを作るのはむずかしいけど、最高に楽しい
役に立ちそうなツールとかリソースとか
教材
- これをやれば簡単なアプリは作れる。実務で使うには足りないと思う(知らない)。このサイトはかなり頻繁にセールをやっているので安いときに買うのがおすすめ。
アイコン
スクリーンショット作成
- スクリーンショット(appstoreのアプリ紹介画像)の作成方法については、良い方法(無料で簡単)が見つかっていない。現状はLaunchKitとAppLaunchpadを組み合わせて作っている。もっと良いツールを知っている人がいたら教えてほしい。Illustratorを勉強するしかないのか...?
感想
- 家族でも友達でもない人に自分の作ったものを使ってもらう、という感覚が不思議で、嬉しい。
- ユーザーからフィードバックがくるのが嬉しい。良い評価だったらもちろん嬉しいし、批判もなんならうれしい。開発やっててよかったと思う瞬間。
- 外国の方が日本語でほめてくれたことがあってめっちゃうれしかった。
その他
githubリポジトリはこちら: https://github.com/MasatoraAtarashi/Shiori
このアプリに関するお問い合わせ・フィードバックは以下のアドレスで受け付けています。
shiori.web.forsafari@gmail.com
- 投稿日:2020-06-02T17:34:29+09:00
設定したフォントが実機で変わってしまう
現象
XibでSystemフォントを設定しているのに、
実機で動かすと別のフォントで表示されている。
そんなこと変えた覚えないんだが…環境
Xcode 11.5
Objective-C
iOS 13.5調査
フォントが変わってしまっているのは、
UITextViewを使っている所だった。対応
フォントが変わっていないUITextViewもあったので違いを確認したら、
コード上でフォントサイズを指定していたので、
フォントが変わってしまった所も指定する様に変更したらSystemフォントで表示された!!修正例:
textView.font = [UIFont systemFontOfSize:16]
あとがき
よくよく調べてみとiOS13のバグらしい…
今回は簡単に自己解決出来たから良かったけど、
こっそり変わってるから半年ほど気づかなかったよ…
- 投稿日:2020-06-02T17:11:56+09:00
R.swiftのスクリプトが意外に遅かったので使用箇所を1モジュールにすることでビルド速度改善
概要
R.swiftを複数のモジュールで使用していたのですが、差分ビルドの遅い箇所を分析するとR.swiftの毎回走るコード生成のスクリプトがリソースの量に関わらず
1箇所毎8秒程 ※差分ビルド・フルビルド関係なく
掛かっていて、何も修正していなくてもビルドする度に時間が掛かっていたのをリソースを1つのモジュールにまとめて、R.swiftのスクリプト実行を1回にすることでビルド速度改善しました。
26秒(3箇所) -> 9秒(1箇所) (-17秒)
【R.swift】
https://github.com/mac-cain13/R.swift環境
- R.swiftバージョン:5.1.0
- Xcode 11.x
- iMac (Quad-Core Intel Core i7 4.2 GHz, メモリ32 GB)
実施した施策
R.swiftを使用するリソースを1つの共通モジュールに集めそこでR.swiftを使用する
他のモジュールでも使用する場合は こちら のように
--accessLevel public
をスクリプトに付けることでpublic
でアクセスできます。
- 投稿日:2020-06-02T17:04:23+09:00
複数のAVPlayerが起動してしまう問題を解決
AVPlayerを終了する時にplayer.replaceCurrentItem(with: nil)を設定する
import UIKit import AVKit class PlayerLayerVC: UIViewController { var playerLayer = AVPlayerLayer() var player : AVPlayer! = AVPlayer() override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) player.replaceCurrentItem(with: nil) } }
- 投稿日:2020-06-02T14:35:17+09:00
コード量を減らさずSwiftファイル数を減らしてビルド速度改善
概要
私の担当するアプリはSwiftファイル数がかなり多い(1モジュール内のSwiftファイル数も結構多い)のですが、
必要なコード量はそのままにSwiftファイル数を減らすことでビルド速度改善ができないかと言う話になりました。環境
- Xcode 11.x
- iMac (Quad-Core Intel Core i7 4.2 GHz, メモリ32 GB)
先に結論
ビルドするマシンの環境依存の影響もあると思いますが、以下のような結果になりました。
可読性もあるのでそこまでファイル数を減らせていないですが、Swiftファイル数減少に応じて多少ビルド速度改善されました。
(一応複数回計測して平均と取っています)[削減したSwiftファイル数]
対応前
4325
対応後
3943 (-382)[ビルド時間計測結果(フルビルド)]
対応前
282秒
対応後
276秒 (-6秒程)やった施策
関連性の強いコードを一つのファイルにする
例えば下記のように特定のファイルに強く依存しているenumを定義しているファイルを
〜Type.swift
enum 〜Type { ... }〜Processor.swift
class 〜Processor { ... }強く依存している方のファイル内で定義する
〜Processor.swift
enum 〜Type { ... } class 〜Processor { ... }短く冗長的なコードを一つにまとめる
短く冗長的なSwiftファイルが複数ある場合
〜Executable.swift
public protocol 〜Executable { ... }〜HogeExecuter.swift
struct 〜HogeExecuter: 〜Executable { (短いコード量) }〜FugaExecuter.swift
struct 〜FugaExecuter: 〜Executable { (短いコード量) }1つのファイルにまとめて定義する
〜Executable.swift
public protocol 〜Executable { ... } struct 〜HogeExecuter: 〜Executable { (短いコード量) } struct 〜FugaExecuter: 〜Executable { (短いコード量) }サイズなどが異なるだけの定義を1つのファイルにまとめる
サイズ毎に定義があるがSwiftファイルが分かれているのを
Small〜Entity.swift
public class Small〜Entity { ... }Medium〜Entity.swift
public class Medium〜Entity: Small〜Entity { ... }Large〜Entity.swift
public class Large〜Entity: Medium〜Entity { ... }1つのファイルにまとめました。
〜Entity.swift
public class Small〜Entity { ... } public class Medium〜Entity: Small〜Entity { ... } public class Large〜Entity: Medium〜Entity { ... }
- 投稿日:2020-06-02T12:00:13+09:00
Firebase Crashlytics の解析について
はじめに
アプリがリリースされてしばらくするとFirebaseから問題があるよって連絡(メール)が来ました。
クラッシュ率の増加で問題があると判断されるんですね。
今回はその調査で苦戦したのでポイントをまとめておきたいと思います。どんな情報がある?
上部に現在のクラッシュの統計情報が表示されて下部にクラッシュの一覧が表示されています。
どうやって調べるの?
まずは青くなってるところをみます。
大抵ここを訳すだけでなんとなく原因がわかると思います。
Fatal Exception: NSInternalInconsistencyException
Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread.google先生にお出ましいただいて
致命的な例外:NSInternalInconsistencyException
メインスレッドからレイアウトエンジンにアクセスした後、レイアウトエンジンの変更をバックグラウンドスレッドから実行しないでください。はい。そうです。ごめんさなさいmm
アプリ開発初心者の時ならみなさん一度は経験あるんじゃないでしょうか?
バックグラウンドスレッドからUIアクセスエラーですね。でスタックトレースの青いところも同じように(TEXTとして)展開して見るとどこのクラスでのクラッシュか情報がありますので場所もある程度特定できます。
ログにはどんな情報が出てるの?
スタックトレースだけじゃわからないこと多いのでログをみてみます。
当然ですがログを出力(コーディング)してないとでません。
個人的にはオペレーションとin/outのデータは欲しいところです。
画面のままじゃ見辛いのでダウンロードしてフィルタするとかして調査すると良いでしょう。ログの大事さ
ログの出力が細かく定義されているプロジェクトもあると思いますが、そうじゃない場合でも必要最低限のログは埋め込むべきです。
私がログを出す時に最低限心がけていることは
- 操作手順がわかる情報を出力する
- エラーが発生する可能性があるところは必ずin/out/(exception)を出力する
- 意味不明(無駄)・間違ってるログは出力しない
です。
1. 操作手順がわかる情報を出力する
例えば画面を開いた時、ボタンを押した時、画面を閉じた時などの操作情報をデバッグログとして埋め込みます。
画面を開いた時や閉じた時はログ出しておいて損はないと思います。MOAspectsとかのライブラリを使ってログイベントを横付けして出力することをお勧めします。そうすることで追加漏れもないでしょうし。
MOAspects.hookInstanceMethod(for: UIViewController.self, selector: #selector(UIViewController.viewWillAppear(_:)), position: .before, range: MOAspectsHookRangeAll) { (object: Any?) in if let object = object { let className = String(describing: type(of: object)) // Firebase Crashlytics のログ出力 CLSLogv("CLSLogv: \(className) viewWillAppear", getVaList([])) // もしくは普通にログ出力 log.debug("\(className) viewWillAppear") } } MOAspects.hookInstanceMethod(for: UIViewController.self, selector: #selector(UIViewController.viewDidDisappear(_:)), position: .before, range: MOAspectsHookRangeAll) { (object: Any?) in if let object = object { let className = String(describing: type(of: object))) // Firebase Crashlytics のログ出力 CLSLogv("CLSLogv: \(className) viewDidDisappear", getVaList([])) // もしくは普通にログ出力 log.debug("\(className) viewDidDisappear") } }あとはこれは必要であればですがViewModelとかのメソッドで処理開始終了とか。
func fetchProcess(id: String) -> FetchResult { log.debug("fetchProcess start") // ・・・略・・・ log.debug("fetchProcess end") return result }2. エラーが発生する可能性があるところは必ずin/out(exception)を出力する
何かの処理でエラーになったけど、どんなパラメータかわからないと原因特定に時間がかかることがあります。なので処理のin/outを出力するようにします。
viewModel.fetchProcess(id: id) .subscribe(onNext: { [weak self] result in switch result { case .success: // 成功したときの処理 case .failure(let error): log.debug("viewModel.fetchProcess error") log.debug("input:id=\(id)") log.debug("error:\(error)") // エラーになったときの処理 } }).disposed(by: disposeBag) viewModel.fetchProcess(id: id) .subscribe(onNext: { result in // 成功したときの処理 }, onError: { error in log.debug("viewModel.fetchProcess error") log.debug("input:id=\(id)") log.debug("error:\(error)") // エラーになったときの処理 }).disposed(by: disposeBag)inputの出力はfetchProcessの中でやってもいいですね。
とにかくエラーになった時の情報はあるに越した事ないです。3. 意味不明(無駄)・間違ってるログは出力しない
意味不明なログは混乱を招くので注意しましょう。
よくあるのがメソッドをコピペで流用作成したのにログの出力が前のメソッドの情報を垂れ流しているとか。func login(id: String) { log.debug("login:start>") // 略 log.debug("login:end>") } func logout(id: String) { log.debug("login:start>") // loginかログアウトかわからない! // 略 log.debug("login:end>") // loginかログアウトかわからない! }どっちやねん?!ってなります!
終わりに
FirebaseのCrashlyticsはクラッシュしたアプリのバージョン・端末・端末のOS・スタックトレース情報・ログなど調査に必要な情報が一通り揃っています。無料の割には高機能ですので積極的に活用していきたいですね。
また、ログの出力を怠っているとトラブルの原因究明に時間がかかってしまいます。
なので横着することなく適切なログを出力を心がけましょう。
初心忘るべからずですね。